happyme531 commited on
Commit
8d28ba8
1 Parent(s): f806722

Upload 9 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* 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
 
 
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
+ output.wav filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,3 +1,131 @@
1
- ---
2
- license: agpl-3.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: agpl-3.0
3
+ language:
4
+ - en
5
+ - zh
6
+ - ja
7
+ - ko
8
+ base_model: lovemefan/SenseVoice-onnx
9
+ tags:
10
+ - rknn
11
+ ---
12
+
13
+ # SenseVoiceSmall-RKNN2
14
+
15
+ ### (English README see below)
16
+
17
+ SenseVoice是具有音频理解能力的音频基础模型, 包括语音识别(ASR)、语种识别(LID)、语音情感识别(SER)和声学事件分类(AEC)或声学事件检测(AED)。
18
+
19
+ 当前SenseVoice-small支持中、粤、英、日、韩语的多语言语音识别,情感识别和事件检测能力,具有极低的推理延迟。
20
+
21
+ - 推理速度(RKNN2):RK3588上单核NPU推理速度约20倍 (每秒识别20秒的音频), 比官方rknn-model-zoo中提供的whisper约快6倍.
22
+ - 内存占用(RKNN2):约1.1GB
23
+
24
+ ## 使用方法
25
+
26
+ 1. 克隆项目到本地
27
+
28
+ 2. 安装依赖
29
+
30
+ ```bash
31
+ pip install kaldi_native_fbank onnxruntime sentencepiece soundfile pyyaml numpy<2
32
+ ```
33
+
34
+ 你还需要手动安装rknn-toolkit2-lite2.
35
+
36
+ 3. 运行
37
+
38
+ ```bash
39
+ python ./sensevoice_rknn.py --audio_file output.wav
40
+ ```
41
+
42
+ 如果使用自己的音频文件测试发现识别不正常,你可能需要提前将它转换为16kHz, 16bit, 单声道的wav格式。
43
+
44
+ ```bash
45
+ ffmpeg -i input.mp3 -f wav -acodec pcm_s16le -ac 1 -ar 16000 output.wav
46
+ ```
47
+
48
+ ## RKNN模型转换
49
+
50
+ 你需要提前安装rknn-toolkit2 v2.1.0或更高版本.
51
+
52
+ 1. 下载或转换onnx模型
53
+
54
+ 可以从 https://huggingface.co/lovemefan/SenseVoice-onnx 下载到onnx模型.
55
+ 应该也可以根据 https://github.com/FunAudioLLM/SenseVoice 中的文档从Pytorch模型转换得到onnx模型.
56
+
57
+ 模型文件应该命名为'sense-voice-encoder.onnx', 放在转换脚本所在目录.
58
+
59
+ 2. 转换为rknn模型
60
+ ```bash
61
+ python convert_rknn.py
62
+ ```
63
+
64
+ ## 已知问题
65
+
66
+ - RKNN2使用fp16推理时可能会出现溢出,导致结果为inf,可以尝试修改输入数据的缩放比例来解决.
67
+ 在`sensevoice_rknn.py`中将`SPEECH_SCALE`设置为更小的值.
68
+
69
+ ## 参考
70
+ - [FunAudioLLM/SenseVoiceSmall](https://huggingface.co/FunAudioLLM/SenseVoiceSmall)
71
+ - [lovemefan/SenseVoice-python](https://github.com/lovemefan/SenseVoice-python)
72
+
73
+ # English README
74
+
75
+ # SenseVoiceSmall-RKNN2
76
+
77
+ SenseVoice is an audio foundation model with audio understanding capabilities, including Automatic Speech Recognition (ASR), Language Identification (LID), Speech Emotion Recognition (SER), and Acoustic Event Classification (AEC) or Acoustic Event Detection (AED).
78
+
79
+ Currently, SenseVoice-small supports multilingual speech recognition, emotion recognition, and event detection for Chinese, Cantonese, English, Japanese, and Korean, with extremely low inference latency.
80
+
81
+ - Inference speed (RKNN2): About 20x real-time on a single NPU core of RK3588 (processing 20 seconds of audio per second), approximately 6 times faster than the official whisper model provided in the rknn-model-zoo.
82
+ - Memory usage (RKNN2): About 1.1GB
83
+
84
+ ## Usage
85
+
86
+ 1. Clone the project to your local machine
87
+
88
+ 2. Install dependencies
89
+
90
+ ```bash
91
+ pip install kaldi_native_fbank onnxruntime sentencepiece soundfile pyyaml numpy<2
92
+ ```
93
+
94
+ You also need to manually install rknn-toolkit2-lite2.
95
+
96
+ 3. Run
97
+
98
+ ```bash
99
+ python ./sensevoice_rknn.py --audio_file output.wav
100
+ ```
101
+
102
+ If you find that recognition is not working correctly when testing with your own audio files, you may need to convert them to 16kHz, 16-bit, mono WAV format in advance.
103
+
104
+ ```bash
105
+ ffmpeg -i input.mp3 -f wav -acodec pcm_s16le -ac 1 -ar 16000 output.wav
106
+ ```
107
+
108
+ ## RKNN Model Conversion
109
+
110
+ You need to install rknn-toolkit2 v2.1.0 or higher in advance.
111
+
112
+ 1. Download or convert the ONNX model
113
+
114
+ You can download the ONNX model from https://huggingface.co/lovemefan/SenseVoice-onnx.
115
+ It should also be possible to convert from a PyTorch model to an ONNX model according to the documentation at https://github.com/FunAudioLLM/SenseVoice.
116
+
117
+ The model file should be named 'sense-voice-encoder.onnx' and placed in the same directory as the conversion script.
118
+
119
+ 2. Convert to RKNN model
120
+ ```bash
121
+ python convert_rknn.py
122
+ ```
123
+
124
+ ## Known Issues
125
+
126
+ - When using fp16 inference with RKNN2, overflow may occur, resulting in inf values. You can try modifying the scaling ratio of the input data to resolve this.
127
+ Set `SPEECH_SCALE` to a smaller value in `sensevoice_rknn.py`.
128
+
129
+ ## References
130
+ - [FunAudioLLM/SenseVoiceSmall](https://huggingface.co/FunAudioLLM/SenseVoiceSmall)
131
+ - [lovemefan/SenseVoice-python](https://github.com/lovemefan/SenseVoice-python)
chn_jpn_yue_eng_ko_spectok.bpe.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aa87f86064c3730d799ddf7af3c04659151102cba548bce325cf06ba4da4e6a8
3
+ size 377341
convert_rknn.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ import os
5
+ from rknn.api import RKNN
6
+ from math import exp
7
+ from sys import exit
8
+ import argparse
9
+ import onnxscript
10
+ from onnxscript.rewriter import pattern
11
+ import onnx.numpy_helper as onh
12
+ import numpy as np
13
+ import onnx
14
+ import onnxruntime as ort
15
+ from rknn.utils import onnx_edit
16
+
17
+ os.chdir(os.path.dirname(os.path.abspath(__file__)))
18
+
19
+ speech_length = 171
20
+
21
+ def convert_encoder():
22
+ rknn = RKNN(verbose=True)
23
+
24
+ ONNX_MODEL=f"sense-voice-encoder.onnx"
25
+ RKNN_MODEL=ONNX_MODEL.replace(".onnx",".rknn")
26
+ DATASET="dataset.txt"
27
+ QUANTIZE=False
28
+
29
+ #开局先给我来个大惊喜,rknn做第一步常量折叠的时候就会在这个子图里报错,所以要单独拿出来先跑一遍
30
+ #然后把这个子图的输出结果保存下来喂给rknn
31
+ onnx.utils.extract_model(ONNX_MODEL, "extract_model.onnx", ['speech_lengths'], ['/make_pad_mask/Cast_2_output_0'])
32
+ sess = ort.InferenceSession("extract_model.onnx", providers=['CPUExecutionProvider'])
33
+ extract_result = sess.run(None, {"speech_lengths": np.array([speech_length], dtype=np.int64)})[0]
34
+
35
+ # 删掉模型最后的多余transpose, 速度从365ms提升到350ms
36
+ ret = onnx_edit(model = ONNX_MODEL,
37
+ export_path = ONNX_MODEL.replace(".onnx", "_edited.onnx"),
38
+ # # 1, len, 25055 -> 1, 25055, 1, len # 这个是坏的, 我真服了,
39
+ # outputs_transform = {'encoder_out': 'a,b,c->a,c,1,b'},
40
+ outputs_transform = {'encoder_out': 'a,b,c->a,c,b'},
41
+ )
42
+ ONNX_MODEL = ONNX_MODEL.replace(".onnx", "_edited.onnx")
43
+
44
+ # pre-process config
45
+ print('--> Config model')
46
+ rknn.config(quantized_algorithm='normal', quantized_method='channel', target_platform='rk3588', optimization_level=3)
47
+ print('done')
48
+
49
+ # Load ONNX model
50
+ print("--> Loading model")
51
+ ret = rknn.load_onnx(
52
+ model=ONNX_MODEL,
53
+ inputs=["speech", "/make_pad_mask/Cast_2_output_0"],
54
+ input_size_list=[[1, speech_length, 560], [extract_result.shape[0], extract_result.shape[1]]],
55
+ input_initial_val=[None, extract_result],
56
+ # outputs=["output"]
57
+ )
58
+
59
+ if ret != 0:
60
+ print('Load model failed!')
61
+ exit(ret)
62
+ print('done')
63
+
64
+ # Build model
65
+ print('--> Building model')
66
+ ret = rknn.build(do_quantization=QUANTIZE, dataset=DATASET, rknn_batch_size=None)
67
+ if ret != 0:
68
+ print('Build model failed!')
69
+ exit(ret)
70
+ print('done')
71
+
72
+ # export
73
+ print('--> Export RKNN model')
74
+ ret = rknn.export_rknn(RKNN_MODEL)
75
+ if ret != 0:
76
+ print('Export RKNN model failed!')
77
+ exit(ret)
78
+ print('done')
79
+
80
+ # usage: python convert_rknn.py encoder|all
81
+
82
+ if __name__ == "__main__":
83
+ parser = argparse.ArgumentParser()
84
+ parser.add_argument("model", type=str, help="model to convert", choices=["encoder", "all"], nargs='?')
85
+ args = parser.parse_args()
86
+ if args.model is None:
87
+ args.model = "all"
88
+
89
+ if args.model == "encoder":
90
+ convert_encoder()
91
+ elif args.model == "all":
92
+ convert_encoder()
93
+ else:
94
+ print(f"Unknown model: {args.model}")
95
+ exit(1)
embedding.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:83cf1fc5680fdf6d7edb411be5ce351cad4eca03b29a5bf5050aa19dfcc12267
3
+ size 35968
fsmn-am.mvn ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <Nnet>
2
+ <Splice> 400 400
3
+ [ 0 ]
4
+ <AddShift> 400 400
5
+ <LearnRateCoef> 0 [ -8.311879 -8.600912 -9.615928 -10.43595 -11.21292 -11.88333 -12.36243 -12.63706 -12.8818 -12.83066 -12.89103 -12.95666 -13.19763 -13.40598 -13.49113 -13.5546 -13.55639 -13.51915 -13.68284 -13.53289 -13.42107 -13.65519 -13.50713 -13.75251 -13.76715 -13.87408 -13.73109 -13.70412 -13.56073 -13.53488 -13.54895 -13.56228 -13.59408 -13.62047 -13.64198 -13.66109 -13.62669 -13.58297 -13.57387 -13.4739 -13.53063 -13.48348 -13.61047 -13.64716 -13.71546 -13.79184 -13.90614 -14.03098 -14.18205 -14.35881 -14.48419 -14.60172 -14.70591 -14.83362 -14.92122 -15.00622 -15.05122 -15.03119 -14.99028 -14.92302 -14.86927 -14.82691 -14.7972 -14.76909 -14.71356 -14.61277 -14.51696 -14.42252 -14.36405 -14.30451 -14.23161 -14.19851 -14.16633 -14.15649 -14.10504 -13.99518 -13.79562 -13.3996 -12.7767 -11.71208 -8.311879 -8.600912 -9.615928 -10.43595 -11.21292 -11.88333 -12.36243 -12.63706 -12.8818 -12.83066 -12.89103 -12.95666 -13.19763 -13.40598 -13.49113 -13.5546 -13.55639 -13.51915 -13.68284 -13.53289 -13.42107 -13.65519 -13.50713 -13.75251 -13.76715 -13.87408 -13.73109 -13.70412 -13.56073 -13.53488 -13.54895 -13.56228 -13.59408 -13.62047 -13.64198 -13.66109 -13.62669 -13.58297 -13.57387 -13.4739 -13.53063 -13.48348 -13.61047 -13.64716 -13.71546 -13.79184 -13.90614 -14.03098 -14.18205 -14.35881 -14.48419 -14.60172 -14.70591 -14.83362 -14.92122 -15.00622 -15.05122 -15.03119 -14.99028 -14.92302 -14.86927 -14.82691 -14.7972 -14.76909 -14.71356 -14.61277 -14.51696 -14.42252 -14.36405 -14.30451 -14.23161 -14.19851 -14.16633 -14.15649 -14.10504 -13.99518 -13.79562 -13.3996 -12.7767 -11.71208 -8.311879 -8.600912 -9.615928 -10.43595 -11.21292 -11.88333 -12.36243 -12.63706 -12.8818 -12.83066 -12.89103 -12.95666 -13.19763 -13.40598 -13.49113 -13.5546 -13.55639 -13.51915 -13.68284 -13.53289 -13.42107 -13.65519 -13.50713 -13.75251 -13.76715 -13.87408 -13.73109 -13.70412 -13.56073 -13.53488 -13.54895 -13.56228 -13.59408 -13.62047 -13.64198 -13.66109 -13.62669 -13.58297 -13.57387 -13.4739 -13.53063 -13.48348 -13.61047 -13.64716 -13.71546 -13.79184 -13.90614 -14.03098 -14.18205 -14.35881 -14.48419 -14.60172 -14.70591 -14.83362 -14.92122 -15.00622 -15.05122 -15.03119 -14.99028 -14.92302 -14.86927 -14.82691 -14.7972 -14.76909 -14.71356 -14.61277 -14.51696 -14.42252 -14.36405 -14.30451 -14.23161 -14.19851 -14.16633 -14.15649 -14.10504 -13.99518 -13.79562 -13.3996 -12.7767 -11.71208 -8.311879 -8.600912 -9.615928 -10.43595 -11.21292 -11.88333 -12.36243 -12.63706 -12.8818 -12.83066 -12.89103 -12.95666 -13.19763 -13.40598 -13.49113 -13.5546 -13.55639 -13.51915 -13.68284 -13.53289 -13.42107 -13.65519 -13.50713 -13.75251 -13.76715 -13.87408 -13.73109 -13.70412 -13.56073 -13.53488 -13.54895 -13.56228 -13.59408 -13.62047 -13.64198 -13.66109 -13.62669 -13.58297 -13.57387 -13.4739 -13.53063 -13.48348 -13.61047 -13.64716 -13.71546 -13.79184 -13.90614 -14.03098 -14.18205 -14.35881 -14.48419 -14.60172 -14.70591 -14.83362 -14.92122 -15.00622 -15.05122 -15.03119 -14.99028 -14.92302 -14.86927 -14.82691 -14.7972 -14.76909 -14.71356 -14.61277 -14.51696 -14.42252 -14.36405 -14.30451 -14.23161 -14.19851 -14.16633 -14.15649 -14.10504 -13.99518 -13.79562 -13.3996 -12.7767 -11.71208 -8.311879 -8.600912 -9.615928 -10.43595 -11.21292 -11.88333 -12.36243 -12.63706 -12.8818 -12.83066 -12.89103 -12.95666 -13.19763 -13.40598 -13.49113 -13.5546 -13.55639 -13.51915 -13.68284 -13.53289 -13.42107 -13.65519 -13.50713 -13.75251 -13.76715 -13.87408 -13.73109 -13.70412 -13.56073 -13.53488 -13.54895 -13.56228 -13.59408 -13.62047 -13.64198 -13.66109 -13.62669 -13.58297 -13.57387 -13.4739 -13.53063 -13.48348 -13.61047 -13.64716 -13.71546 -13.79184 -13.90614 -14.03098 -14.18205 -14.35881 -14.48419 -14.60172 -14.70591 -14.83362 -14.92122 -15.00622 -15.05122 -15.03119 -14.99028 -14.92302 -14.86927 -14.82691 -14.7972 -14.76909 -14.71356 -14.61277 -14.51696 -14.42252 -14.36405 -14.30451 -14.23161 -14.19851 -14.16633 -14.15649 -14.10504 -13.99518 -13.79562 -13.3996 -12.7767 -11.71208 ]
6
+ <Rescale> 400 400
7
+ <LearnRateCoef> 0 [ 0.155775 0.154484 0.1527379 0.1518718 0.1506028 0.1489256 0.147067 0.1447061 0.1436307 0.1443568 0.1451849 0.1455157 0.1452821 0.1445717 0.1439195 0.1435867 0.1436018 0.1438781 0.1442086 0.1448844 0.1454756 0.145663 0.146268 0.1467386 0.1472724 0.147664 0.1480913 0.1483739 0.1488841 0.1493636 0.1497088 0.1500379 0.1502916 0.1505389 0.1506787 0.1507102 0.1505992 0.1505445 0.1505938 0.1508133 0.1509569 0.1512396 0.1514625 0.1516195 0.1516156 0.1515561 0.1514966 0.1513976 0.1512612 0.151076 0.1510596 0.1510431 0.151077 0.1511168 0.1511917 0.151023 0.1508045 0.1505885 0.1503493 0.1502373 0.1501726 0.1500762 0.1500065 0.1499782 0.150057 0.1502658 0.150469 0.1505335 0.1505505 0.1505328 0.1504275 0.1502438 0.1499674 0.1497118 0.1494661 0.1493102 0.1493681 0.1495501 0.1499738 0.1509654 0.155775 0.154484 0.1527379 0.1518718 0.1506028 0.1489256 0.147067 0.1447061 0.1436307 0.1443568 0.1451849 0.1455157 0.1452821 0.1445717 0.1439195 0.1435867 0.1436018 0.1438781 0.1442086 0.1448844 0.1454756 0.145663 0.146268 0.1467386 0.1472724 0.147664 0.1480913 0.1483739 0.1488841 0.1493636 0.1497088 0.1500379 0.1502916 0.1505389 0.1506787 0.1507102 0.1505992 0.1505445 0.1505938 0.1508133 0.1509569 0.1512396 0.1514625 0.1516195 0.1516156 0.1515561 0.1514966 0.1513976 0.1512612 0.151076 0.1510596 0.1510431 0.151077 0.1511168 0.1511917 0.151023 0.1508045 0.1505885 0.1503493 0.1502373 0.1501726 0.1500762 0.1500065 0.1499782 0.150057 0.1502658 0.150469 0.1505335 0.1505505 0.1505328 0.1504275 0.1502438 0.1499674 0.1497118 0.1494661 0.1493102 0.1493681 0.1495501 0.1499738 0.1509654 0.155775 0.154484 0.1527379 0.1518718 0.1506028 0.1489256 0.147067 0.1447061 0.1436307 0.1443568 0.1451849 0.1455157 0.1452821 0.1445717 0.1439195 0.1435867 0.1436018 0.1438781 0.1442086 0.1448844 0.1454756 0.145663 0.146268 0.1467386 0.1472724 0.147664 0.1480913 0.1483739 0.1488841 0.1493636 0.1497088 0.1500379 0.1502916 0.1505389 0.1506787 0.1507102 0.1505992 0.1505445 0.1505938 0.1508133 0.1509569 0.1512396 0.1514625 0.1516195 0.1516156 0.1515561 0.1514966 0.1513976 0.1512612 0.151076 0.1510596 0.1510431 0.151077 0.1511168 0.1511917 0.151023 0.1508045 0.1505885 0.1503493 0.1502373 0.1501726 0.1500762 0.1500065 0.1499782 0.150057 0.1502658 0.150469 0.1505335 0.1505505 0.1505328 0.1504275 0.1502438 0.1499674 0.1497118 0.1494661 0.1493102 0.1493681 0.1495501 0.1499738 0.1509654 0.155775 0.154484 0.1527379 0.1518718 0.1506028 0.1489256 0.147067 0.1447061 0.1436307 0.1443568 0.1451849 0.1455157 0.1452821 0.1445717 0.1439195 0.1435867 0.1436018 0.1438781 0.1442086 0.1448844 0.1454756 0.145663 0.146268 0.1467386 0.1472724 0.147664 0.1480913 0.1483739 0.1488841 0.1493636 0.1497088 0.1500379 0.1502916 0.1505389 0.1506787 0.1507102 0.1505992 0.1505445 0.1505938 0.1508133 0.1509569 0.1512396 0.1514625 0.1516195 0.1516156 0.1515561 0.1514966 0.1513976 0.1512612 0.151076 0.1510596 0.1510431 0.151077 0.1511168 0.1511917 0.151023 0.1508045 0.1505885 0.1503493 0.1502373 0.1501726 0.1500762 0.1500065 0.1499782 0.150057 0.1502658 0.150469 0.1505335 0.1505505 0.1505328 0.1504275 0.1502438 0.1499674 0.1497118 0.1494661 0.1493102 0.1493681 0.1495501 0.1499738 0.1509654 0.155775 0.154484 0.1527379 0.1518718 0.1506028 0.1489256 0.147067 0.1447061 0.1436307 0.1443568 0.1451849 0.1455157 0.1452821 0.1445717 0.1439195 0.1435867 0.1436018 0.1438781 0.1442086 0.1448844 0.1454756 0.145663 0.146268 0.1467386 0.1472724 0.147664 0.1480913 0.1483739 0.1488841 0.1493636 0.1497088 0.1500379 0.1502916 0.1505389 0.1506787 0.1507102 0.1505992 0.1505445 0.1505938 0.1508133 0.1509569 0.1512396 0.1514625 0.1516195 0.1516156 0.1515561 0.1514966 0.1513976 0.1512612 0.151076 0.1510596 0.1510431 0.151077 0.1511168 0.1511917 0.151023 0.1508045 0.1505885 0.1503493 0.1502373 0.1501726 0.1500762 0.1500065 0.1499782 0.150057 0.1502658 0.150469 0.1505335 0.1505505 0.1505328 0.1504275 0.1502438 0.1499674 0.1497118 0.1494661 0.1493102 0.1493681 0.1495501 0.1499738 0.1509654 ]
8
+ </Nnet>
fsmn-config.yaml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ WavFrontend:
2
+ frontend_conf:
3
+ fs: 16000
4
+ window: hamming
5
+ n_mels: 80
6
+ frame_length: 25
7
+ frame_shift: 10
8
+ dither: 0.0
9
+ lfr_m: 5
10
+ lfr_n: 1
11
+
12
+ FSMN:
13
+ use_cuda: False
14
+ CUDAExecutionProvider:
15
+ device_id: 0
16
+ arena_extend_strategy: kNextPowerOfTwo
17
+ cudnn_conv_algo_search: EXHAUSTIVE
18
+ do_copy_in_default_stream: true
19
+ encoder_conf:
20
+ input_dim: 400
21
+ input_affine_dim: 140
22
+ fsmn_layers: 4
23
+ linear_dim: 250
24
+ proj_dim: 128
25
+ lorder: 20
26
+ rorder: 0
27
+ lstride: 1
28
+ rstride: 0
29
+ output_affine_dim: 140
30
+ output_dim: 248
31
+
32
+ vadPostArgs:
33
+ sample_rate: 16000
34
+ detect_mode: 1
35
+ snr_mode: 0
36
+ max_end_silence_time: 800
37
+ max_start_silence_time: 3000
38
+ do_start_point_detection: True
39
+ do_end_point_detection: True
40
+ window_size_ms: 200
41
+ sil_to_speech_time_thres: 150
42
+ speech_to_sil_time_thres: 150
43
+ speech_2_noise_ratio: 1.0
44
+ do_extend: 1
45
+ lookback_time_start_point: 200
46
+ lookahead_time_end_point: 100
47
+ max_single_segment_time: 10000
48
+ snr_thres: -100.0
49
+ noise_frame_num_used_for_snr: 100
50
+ decibel_thres: -100.0
51
+ speech_noise_thres: 0.6
52
+ fe_prior_thres: 0.0001
53
+ silence_pdf_num: 1
54
+ sil_pdf_ids: [ 0 ]
55
+ speech_noise_thresh_low: -0.1
56
+ speech_noise_thresh_high: 0.3
57
+ output_frame_probs: False
58
+ frame_in_ms: 10
59
+ frame_length_ms: 25
fsmnvad-offline.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4bbd68b11519e916b6871ff6f8df15e2100936b256be9cb104cd63fb7c859965
3
+ size 1725472
output.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b6f02d2c58b9a8a294a306ccb60bdf667587d74984915a3ec87a6de5e04bb020
3
+ size 1289994
sensevoice_rknn.py ADDED
@@ -0,0 +1,1402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # File: onnx/fsmn_vad_ort_session.py
2
+ # ```py
3
+
4
+ # -*- coding:utf-8 -*-
5
+ # @FileName :fsmn_vad_ort_session.py.py
6
+ # @Time :2024/8/31 16:45
7
+ # @Author :lovemefan
8
+ # @Email :lovemefan@outlook.com
9
+
10
+ import argparse
11
+ import logging
12
+ import math
13
+ import os
14
+ import time
15
+ import warnings
16
+ from enum import Enum
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Tuple, Union
19
+
20
+ import kaldi_native_fbank as knf
21
+ import numpy as np
22
+ import sentencepiece as spm
23
+ import soundfile as sf
24
+ import yaml
25
+ from onnxruntime import (GraphOptimizationLevel, InferenceSession,
26
+ SessionOptions, get_available_providers, get_device)
27
+ from rknnlite.api.rknn_lite import RKNNLite
28
+
29
+ RKNN_INPUT_LEN = 171
30
+
31
+ SPEECH_SCALE = 1/2 # 因为是fp16推理,如果中间结果太大可能会溢出变inf,所以需要缩放一下
32
+
33
+ class VadOrtInferRuntimeSession:
34
+ def __init__(self, config, root_dir: Path):
35
+ sess_opt = SessionOptions()
36
+ sess_opt.log_severity_level = 4
37
+ sess_opt.enable_cpu_mem_arena = False
38
+ sess_opt.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL
39
+
40
+ cuda_ep = "CUDAExecutionProvider"
41
+ cpu_ep = "CPUExecutionProvider"
42
+ cpu_provider_options = {
43
+ "arena_extend_strategy": "kSameAsRequested",
44
+ }
45
+
46
+ EP_list = []
47
+ if (
48
+ config["use_cuda"]
49
+ and get_device() == "GPU"
50
+ and cuda_ep in get_available_providers()
51
+ ):
52
+ EP_list = [(cuda_ep, config[cuda_ep])]
53
+ EP_list.append((cpu_ep, cpu_provider_options))
54
+
55
+ config["model_path"] = root_dir / str(config["model_path"])
56
+ self._verify_model(config["model_path"])
57
+ logging.info(f"Loading onnx model at {str(config['model_path'])}")
58
+ self.session = InferenceSession(
59
+ str(config["model_path"]), sess_options=sess_opt, providers=EP_list
60
+ )
61
+
62
+ if config["use_cuda"] and cuda_ep not in self.session.get_providers():
63
+ logging.warning(
64
+ f"{cuda_ep} is not available for current env, "
65
+ f"the inference part is automatically shifted to be "
66
+ f"executed under {cpu_ep}.\n "
67
+ "Please ensure the installed onnxruntime-gpu version"
68
+ " matches your cuda and cudnn version, "
69
+ "you can check their relations from the offical web site: "
70
+ "https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html",
71
+ RuntimeWarning,
72
+ )
73
+
74
+ def __call__(
75
+ self, input_content: List[Union[np.ndarray, np.ndarray]]
76
+ ) -> np.ndarray:
77
+ if isinstance(input_content, list):
78
+ input_dict = {
79
+ "speech": input_content[0],
80
+ "in_cache0": input_content[1],
81
+ "in_cache1": input_content[2],
82
+ "in_cache2": input_content[3],
83
+ "in_cache3": input_content[4],
84
+ }
85
+ else:
86
+ input_dict = {"speech": input_content}
87
+
88
+ return self.session.run(None, input_dict)
89
+
90
+ def get_input_names(
91
+ self,
92
+ ):
93
+ return [v.name for v in self.session.get_inputs()]
94
+
95
+ def get_output_names(
96
+ self,
97
+ ):
98
+ return [v.name for v in self.session.get_outputs()]
99
+
100
+ def get_character_list(self, key: str = "character"):
101
+ return self.meta_dict[key].splitlines()
102
+
103
+ def have_key(self, key: str = "character") -> bool:
104
+ self.meta_dict = self.session.get_modelmeta().custom_metadata_map
105
+ if key in self.meta_dict.keys():
106
+ return True
107
+ return False
108
+
109
+ @staticmethod
110
+ def _verify_model(model_path):
111
+ model_path = Path(model_path)
112
+ if not model_path.exists():
113
+ raise FileNotFoundError(f"{model_path} does not exists.")
114
+ if not model_path.is_file():
115
+ raise FileExistsError(f"{model_path} is not a file.")
116
+
117
+ # ```
118
+
119
+ # File: onnx/sense_voice_ort_session.py
120
+ # ```py
121
+ # -*- coding:utf-8 -*-
122
+ # @FileName :sense_voice_onnxruntime.py
123
+ # @Time :2024/7/17 20:53
124
+ # @Author :lovemefan
125
+ # @Email :lovemefan@outlook.com
126
+
127
+
128
+ formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
129
+ logging.basicConfig(format=formatter, level=logging.INFO)
130
+
131
+
132
+ class OrtInferRuntimeSession:
133
+ def __init__(self, model_file, device_id=-1, intra_op_num_threads=4):
134
+ device_id = str(device_id)
135
+ sess_opt = SessionOptions()
136
+ sess_opt.intra_op_num_threads = intra_op_num_threads
137
+ sess_opt.log_severity_level = 4
138
+ sess_opt.enable_cpu_mem_arena = False
139
+ sess_opt.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL
140
+
141
+ cuda_ep = "CUDAExecutionProvider"
142
+ cuda_provider_options = {
143
+ "device_id": device_id,
144
+ "arena_extend_strategy": "kNextPowerOfTwo",
145
+ "cudnn_conv_algo_search": "EXHAUSTIVE",
146
+ "do_copy_in_default_stream": "true",
147
+ }
148
+ cpu_ep = "CPUExecutionProvider"
149
+ cpu_provider_options = {
150
+ "arena_extend_strategy": "kSameAsRequested",
151
+ }
152
+
153
+ EP_list = []
154
+ if (
155
+ device_id != "-1"
156
+ and get_device() == "GPU"
157
+ and cuda_ep in get_available_providers()
158
+ ):
159
+ EP_list = [(cuda_ep, cuda_provider_options)]
160
+ EP_list.append((cpu_ep, cpu_provider_options))
161
+
162
+ self._verify_model(model_file)
163
+
164
+ self.session = InferenceSession(
165
+ model_file, sess_options=sess_opt, providers=EP_list
166
+ )
167
+
168
+ # delete binary of model file to save memory
169
+ del model_file
170
+
171
+ if device_id != "-1" and cuda_ep not in self.session.get_providers():
172
+ warnings.warn(
173
+ f"{cuda_ep} is not avaiable for current env, the inference part is automatically shifted to be executed under {cpu_ep}.\n"
174
+ "Please ensure the installed onnxruntime-gpu version matches your cuda and cudnn version, "
175
+ "you can check their relations from the offical web site: "
176
+ "https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html",
177
+ RuntimeWarning,
178
+ )
179
+
180
+ def __call__(self, input_content) -> np.ndarray:
181
+ input_dict = dict(zip(self.get_input_names(), input_content))
182
+ try:
183
+ result = self.session.run(self.get_output_names(), input_dict)
184
+ return result
185
+ except Exception as e:
186
+ print(e)
187
+ raise RuntimeError(f"ONNXRuntime inferece failed. ") from e
188
+
189
+ def get_input_names(
190
+ self,
191
+ ):
192
+ return [v.name for v in self.session.get_inputs()]
193
+
194
+ def get_output_names(
195
+ self,
196
+ ):
197
+ return [v.name for v in self.session.get_outputs()]
198
+
199
+ def get_character_list(self, key: str = "character"):
200
+ return self.meta_dict[key].splitlines()
201
+
202
+ def have_key(self, key: str = "character") -> bool:
203
+ self.meta_dict = self.session.get_modelmeta().custom_metadata_map
204
+ if key in self.meta_dict.keys():
205
+ return True
206
+ return False
207
+
208
+ @staticmethod
209
+ def _verify_model(model_path):
210
+ model_path = Path(model_path)
211
+ if not model_path.exists():
212
+ raise FileNotFoundError(f"{model_path} does not exists.")
213
+ if not model_path.is_file():
214
+ raise FileExistsError(f"{model_path} is not a file.")
215
+
216
+
217
+ def log_softmax(x: np.ndarray) -> np.ndarray:
218
+ # Subtract the maximum value in each row for numerical stability
219
+ x_max = np.max(x, axis=-1, keepdims=True)
220
+ # Calculate the softmax of x
221
+ softmax = np.exp(x - x_max)
222
+ softmax_sum = np.sum(softmax, axis=-1, keepdims=True)
223
+ softmax = softmax / softmax_sum
224
+ # Calculate the log of the softmax values
225
+ return np.log(softmax)
226
+
227
+
228
+ class SenseVoiceInferenceSession:
229
+ def __init__(
230
+ self,
231
+ embedding_model_file,
232
+ encoder_model_file,
233
+ bpe_model_file,
234
+ device_id=-1,
235
+ intra_op_num_threads=4,
236
+ ):
237
+ logging.info(f"Loading model from {embedding_model_file}")
238
+
239
+ self.embedding = np.load(embedding_model_file)
240
+ logging.info(f"Loading model {encoder_model_file}")
241
+ start = time.time()
242
+ self.encoder = RKNNLite(verbose=False)
243
+ self.encoder.load_rknn(encoder_model_file)
244
+ self.encoder.init_runtime()
245
+
246
+ logging.info(
247
+ f"Loading {encoder_model_file} takes {time.time() - start:.2f} seconds"
248
+ )
249
+ self.blank_id = 0
250
+ self.sp = spm.SentencePieceProcessor()
251
+ self.sp.load(bpe_model_file)
252
+
253
+ def __call__(self, speech, language: int, use_itn: bool) -> np.ndarray:
254
+ language_query = self.embedding[[[language]]]
255
+
256
+ # 14 means with itn, 15 means without itn
257
+ text_norm_query = self.embedding[[[14 if use_itn else 15]]]
258
+ event_emo_query = self.embedding[[[1, 2]]]
259
+
260
+ # scale the speech
261
+ speech = speech * SPEECH_SCALE
262
+
263
+ input_content = np.concatenate(
264
+ [
265
+ language_query,
266
+ event_emo_query,
267
+ text_norm_query,
268
+ speech,
269
+ ],
270
+ axis=1,
271
+ ).astype(np.float32)
272
+ print(input_content.shape)
273
+ # pad [1, len, ...] to [1, RKNN_INPUT_LEN, ... ]
274
+ input_content = np.pad(input_content, ((0, 0), (0, RKNN_INPUT_LEN - input_content.shape[1]), (0, 0)))
275
+ print("padded shape:", input_content.shape)
276
+ start_time = time.time()
277
+ encoder_out = self.encoder.inference(inputs=[input_content])[0]
278
+ end_time = time.time()
279
+ print(f"encoder inference time: {end_time - start_time:.2f} seconds")
280
+ # print(encoder_out)
281
+ def unique_consecutive(arr):
282
+ if len(arr) == 0:
283
+ return arr
284
+ # Create a boolean mask where True indicates the element is different from the previous one
285
+ mask = np.append([True], arr[1:] != arr[:-1])
286
+ out = arr[mask]
287
+ out = out[out != self.blank_id]
288
+ return out.tolist()
289
+
290
+ #现在shape变成了1, n_vocab, n_seq. 这里axis需要改一下
291
+ # hypos = unique_consecutive(encoder_out[0].argmax(axis=-1))
292
+ hypos = unique_consecutive(encoder_out[0].argmax(axis=0))
293
+ text = self.sp.DecodeIds(hypos)
294
+ return text
295
+
296
+ # ```
297
+
298
+ # File: utils/frontend.py
299
+ # ```py
300
+ # -*- coding:utf-8 -*-
301
+ # @FileName :frontend.py
302
+ # @Time :2024/7/18 09:39
303
+ # @Author :lovemefan
304
+ # @Email :lovemefan@outlook.com
305
+
306
+ class WavFrontend:
307
+ """Conventional frontend structure for ASR."""
308
+
309
+ def __init__(
310
+ self,
311
+ cmvn_file: str = None,
312
+ fs: int = 16000,
313
+ window: str = "hamming",
314
+ n_mels: int = 80,
315
+ frame_length: int = 25,
316
+ frame_shift: int = 10,
317
+ lfr_m: int = 7,
318
+ lfr_n: int = 6,
319
+ dither: float = 0,
320
+ **kwargs,
321
+ ) -> None:
322
+ opts = knf.FbankOptions()
323
+ opts.frame_opts.samp_freq = fs
324
+ opts.frame_opts.dither = dither
325
+ opts.frame_opts.window_type = window
326
+ opts.frame_opts.frame_shift_ms = float(frame_shift)
327
+ opts.frame_opts.frame_length_ms = float(frame_length)
328
+ opts.mel_opts.num_bins = n_mels
329
+ opts.energy_floor = 0
330
+ opts.frame_opts.snip_edges = True
331
+ opts.mel_opts.debug_mel = False
332
+ self.opts = opts
333
+
334
+ self.lfr_m = lfr_m
335
+ self.lfr_n = lfr_n
336
+ self.cmvn_file = cmvn_file
337
+
338
+ if self.cmvn_file:
339
+ self.cmvn = self.load_cmvn()
340
+ self.fbank_fn = None
341
+ self.fbank_beg_idx = 0
342
+ self.reset_status()
343
+
344
+ def reset_status(self):
345
+ self.fbank_fn = knf.OnlineFbank(self.opts)
346
+ self.fbank_beg_idx = 0
347
+
348
+ def fbank(self, waveform: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
349
+ waveform = waveform * (1 << 15)
350
+ self.fbank_fn = knf.OnlineFbank(self.opts)
351
+ self.fbank_fn.accept_waveform(self.opts.frame_opts.samp_freq, waveform.tolist())
352
+ frames = self.fbank_fn.num_frames_ready
353
+ mat = np.empty([frames, self.opts.mel_opts.num_bins])
354
+ for i in range(frames):
355
+ mat[i, :] = self.fbank_fn.get_frame(i)
356
+ feat = mat.astype(np.float32)
357
+ feat_len = np.array(mat.shape[0]).astype(np.int32)
358
+ return feat, feat_len
359
+
360
+ def lfr_cmvn(self, feat: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
361
+ if self.lfr_m != 1 or self.lfr_n != 1:
362
+ feat = self.apply_lfr(feat, self.lfr_m, self.lfr_n)
363
+
364
+ if self.cmvn_file:
365
+ feat = self.apply_cmvn(feat)
366
+
367
+ feat_len = np.array(feat.shape[0]).astype(np.int32)
368
+ return feat, feat_len
369
+
370
+ def load_audio(self, filename: str) -> Tuple[np.ndarray, int]:
371
+ data, sample_rate = sf.read(
372
+ filename,
373
+ always_2d=True,
374
+ dtype="float32",
375
+ )
376
+ assert (
377
+ sample_rate == 16000
378
+ ), f"Only 16000 Hz is supported, but got {sample_rate}Hz"
379
+ self.sample_rate = sample_rate
380
+ data = data[:, 0] # use only the first channel
381
+ samples = np.ascontiguousarray(data)
382
+
383
+ return samples, sample_rate
384
+
385
+ @staticmethod
386
+ def apply_lfr(inputs: np.ndarray, lfr_m: int, lfr_n: int) -> np.ndarray:
387
+ LFR_inputs = []
388
+
389
+ T = inputs.shape[0]
390
+ T_lfr = int(np.ceil(T / lfr_n))
391
+ left_padding = np.tile(inputs[0], ((lfr_m - 1) // 2, 1))
392
+ inputs = np.vstack((left_padding, inputs))
393
+ T = T + (lfr_m - 1) // 2
394
+ for i in range(T_lfr):
395
+ if lfr_m <= T - i * lfr_n:
396
+ LFR_inputs.append(
397
+ (inputs[i * lfr_n : i * lfr_n + lfr_m]).reshape(1, -1)
398
+ )
399
+ else:
400
+ # process last LFR frame
401
+ num_padding = lfr_m - (T - i * lfr_n)
402
+ frame = inputs[i * lfr_n :].reshape(-1)
403
+ for _ in range(num_padding):
404
+ frame = np.hstack((frame, inputs[-1]))
405
+
406
+ LFR_inputs.append(frame)
407
+ LFR_outputs = np.vstack(LFR_inputs).astype(np.float32)
408
+ return LFR_outputs
409
+
410
+ def apply_cmvn(self, inputs: np.ndarray) -> np.ndarray:
411
+ """
412
+ Apply CMVN with mvn data
413
+ """
414
+ frame, dim = inputs.shape
415
+ means = np.tile(self.cmvn[0:1, :dim], (frame, 1))
416
+ vars = np.tile(self.cmvn[1:2, :dim], (frame, 1))
417
+ inputs = (inputs + means) * vars
418
+ return inputs
419
+
420
+ def get_features(self, inputs: Union[str, np.ndarray]) -> Tuple[np.ndarray, int]:
421
+ if isinstance(inputs, str):
422
+ inputs, _ = self.load_audio(inputs)
423
+
424
+ fbank, _ = self.fbank(inputs)
425
+ feats = self.apply_cmvn(self.apply_lfr(fbank, self.lfr_m, self.lfr_n))
426
+ return feats
427
+
428
+ def load_cmvn(
429
+ self,
430
+ ) -> np.ndarray:
431
+ with open(self.cmvn_file, "r", encoding="utf-8") as f:
432
+ lines = f.readlines()
433
+
434
+ means_list = []
435
+ vars_list = []
436
+ for i in range(len(lines)):
437
+ line_item = lines[i].split()
438
+ if line_item[0] == "<AddShift>":
439
+ line_item = lines[i + 1].split()
440
+ if line_item[0] == "<LearnRateCoef>":
441
+ add_shift_line = line_item[3 : (len(line_item) - 1)]
442
+ means_list = list(add_shift_line)
443
+ continue
444
+ elif line_item[0] == "<Rescale>":
445
+ line_item = lines[i + 1].split()
446
+ if line_item[0] == "<LearnRateCoef>":
447
+ rescale_line = line_item[3 : (len(line_item) - 1)]
448
+ vars_list = list(rescale_line)
449
+ continue
450
+
451
+ means = np.array(means_list).astype(np.float64)
452
+ vars = np.array(vars_list).astype(np.float64)
453
+ cmvn = np.array([means, vars])
454
+ return cmvn
455
+
456
+ # ```
457
+
458
+ # File: utils/fsmn_vad.py
459
+ # ```py
460
+ # -*- coding:utf-8 -*-
461
+ # @FileName :fsmn_vad.py
462
+ # @Time :2024/8/31 16:50
463
+ # @Author :lovemefan
464
+ # @Email :lovemefan@outlook.com
465
+
466
+
467
+
468
+ def read_yaml(yaml_path: Union[str, Path]) -> Dict:
469
+ if not Path(yaml_path).exists():
470
+ raise FileExistsError(f"The {yaml_path} does not exist.")
471
+
472
+ with open(str(yaml_path), "rb") as f:
473
+ data = yaml.load(f, Loader=yaml.Loader)
474
+ return data
475
+
476
+
477
+ class VadStateMachine(Enum):
478
+ kVadInStateStartPointNotDetected = 1
479
+ kVadInStateInSpeechSegment = 2
480
+ kVadInStateEndPointDetected = 3
481
+
482
+
483
+ class FrameState(Enum):
484
+ kFrameStateInvalid = -1
485
+ kFrameStateSpeech = 1
486
+ kFrameStateSil = 0
487
+
488
+
489
+ # final voice/unvoice state per frame
490
+ class AudioChangeState(Enum):
491
+ kChangeStateSpeech2Speech = 0
492
+ kChangeStateSpeech2Sil = 1
493
+ kChangeStateSil2Sil = 2
494
+ kChangeStateSil2Speech = 3
495
+ kChangeStateNoBegin = 4
496
+ kChangeStateInvalid = 5
497
+
498
+
499
+ class VadDetectMode(Enum):
500
+ kVadSingleUtteranceDetectMode = 0
501
+ kVadMutipleUtteranceDetectMode = 1
502
+
503
+
504
+ class VADXOptions:
505
+ def __init__(
506
+ self,
507
+ sample_rate: int = 16000,
508
+ detect_mode: int = VadDetectMode.kVadMutipleUtteranceDetectMode.value,
509
+ snr_mode: int = 0,
510
+ max_end_silence_time: int = 800,
511
+ max_start_silence_time: int = 3000,
512
+ do_start_point_detection: bool = True,
513
+ do_end_point_detection: bool = True,
514
+ window_size_ms: int = 200,
515
+ sil_to_speech_time_thres: int = 150,
516
+ speech_to_sil_time_thres: int = 150,
517
+ speech_2_noise_ratio: float = 1.0,
518
+ do_extend: int = 1,
519
+ lookback_time_start_point: int = 200,
520
+ lookahead_time_end_point: int = 100,
521
+ max_single_segment_time: int = 60000,
522
+ nn_eval_block_size: int = 8,
523
+ dcd_block_size: int = 4,
524
+ snr_thres: int = -100.0,
525
+ noise_frame_num_used_for_snr: int = 100,
526
+ decibel_thres: int = -100.0,
527
+ speech_noise_thres: float = 0.6,
528
+ fe_prior_thres: float = 1e-4,
529
+ silence_pdf_num: int = 1,
530
+ sil_pdf_ids: List[int] = [0],
531
+ speech_noise_thresh_low: float = -0.1,
532
+ speech_noise_thresh_high: float = 0.3,
533
+ output_frame_probs: bool = False,
534
+ frame_in_ms: int = 10,
535
+ frame_length_ms: int = 25,
536
+ ):
537
+ self.sample_rate = sample_rate
538
+ self.detect_mode = detect_mode
539
+ self.snr_mode = snr_mode
540
+ self.max_end_silence_time = max_end_silence_time
541
+ self.max_start_silence_time = max_start_silence_time
542
+ self.do_start_point_detection = do_start_point_detection
543
+ self.do_end_point_detection = do_end_point_detection
544
+ self.window_size_ms = window_size_ms
545
+ self.sil_to_speech_time_thres = sil_to_speech_time_thres
546
+ self.speech_to_sil_time_thres = speech_to_sil_time_thres
547
+ self.speech_2_noise_ratio = speech_2_noise_ratio
548
+ self.do_extend = do_extend
549
+ self.lookback_time_start_point = lookback_time_start_point
550
+ self.lookahead_time_end_point = lookahead_time_end_point
551
+ self.max_single_segment_time = max_single_segment_time
552
+ self.nn_eval_block_size = nn_eval_block_size
553
+ self.dcd_block_size = dcd_block_size
554
+ self.snr_thres = snr_thres
555
+ self.noise_frame_num_used_for_snr = noise_frame_num_used_for_snr
556
+ self.decibel_thres = decibel_thres
557
+ self.speech_noise_thres = speech_noise_thres
558
+ self.fe_prior_thres = fe_prior_thres
559
+ self.silence_pdf_num = silence_pdf_num
560
+ self.sil_pdf_ids = sil_pdf_ids
561
+ self.speech_noise_thresh_low = speech_noise_thresh_low
562
+ self.speech_noise_thresh_high = speech_noise_thresh_high
563
+ self.output_frame_probs = output_frame_probs
564
+ self.frame_in_ms = frame_in_ms
565
+ self.frame_length_ms = frame_length_ms
566
+
567
+
568
+ class E2EVadSpeechBufWithDoa(object):
569
+ def __init__(self):
570
+ self.start_ms = 0
571
+ self.end_ms = 0
572
+ self.buffer = []
573
+ self.contain_seg_start_point = False
574
+ self.contain_seg_end_point = False
575
+ self.doa = 0
576
+
577
+ def reset(self):
578
+ self.start_ms = 0
579
+ self.end_ms = 0
580
+ self.buffer = []
581
+ self.contain_seg_start_point = False
582
+ self.contain_seg_end_point = False
583
+ self.doa = 0
584
+
585
+
586
+ class E2EVadFrameProb(object):
587
+ def __init__(self):
588
+ self.noise_prob = 0.0
589
+ self.speech_prob = 0.0
590
+ self.score = 0.0
591
+ self.frame_id = 0
592
+ self.frm_state = 0
593
+
594
+
595
+ class WindowDetector(object):
596
+ def __init__(
597
+ self,
598
+ window_size_ms: int,
599
+ sil_to_speech_time: int,
600
+ speech_to_sil_time: int,
601
+ frame_size_ms: int,
602
+ ):
603
+ self.window_size_ms = window_size_ms
604
+ self.sil_to_speech_time = sil_to_speech_time
605
+ self.speech_to_sil_time = speech_to_sil_time
606
+ self.frame_size_ms = frame_size_ms
607
+
608
+ self.win_size_frame = int(window_size_ms / frame_size_ms)
609
+ self.win_sum = 0
610
+ self.win_state = [0] * self.win_size_frame # 初始化窗
611
+
612
+ self.cur_win_pos = 0
613
+ self.pre_frame_state = FrameState.kFrameStateSil
614
+ self.cur_frame_state = FrameState.kFrameStateSil
615
+ self.sil_to_speech_frmcnt_thres = int(sil_to_speech_time / frame_size_ms)
616
+ self.speech_to_sil_frmcnt_thres = int(speech_to_sil_time / frame_size_ms)
617
+
618
+ self.voice_last_frame_count = 0
619
+ self.noise_last_frame_count = 0
620
+ self.hydre_frame_count = 0
621
+
622
+ def reset(self) -> None:
623
+ self.cur_win_pos = 0
624
+ self.win_sum = 0
625
+ self.win_state = [0] * self.win_size_frame
626
+ self.pre_frame_state = FrameState.kFrameStateSil
627
+ self.cur_frame_state = FrameState.kFrameStateSil
628
+ self.voice_last_frame_count = 0
629
+ self.noise_last_frame_count = 0
630
+ self.hydre_frame_count = 0
631
+
632
+ def get_win_size(self) -> int:
633
+ return int(self.win_size_frame)
634
+
635
+ def detect_one_frame(
636
+ self, frameState: FrameState, frame_count: int
637
+ ) -> AudioChangeState:
638
+ cur_frame_state = FrameState.kFrameStateSil
639
+ if frameState == FrameState.kFrameStateSpeech:
640
+ cur_frame_state = 1
641
+ elif frameState == FrameState.kFrameStateSil:
642
+ cur_frame_state = 0
643
+ else:
644
+ return AudioChangeState.kChangeStateInvalid
645
+ self.win_sum -= self.win_state[self.cur_win_pos]
646
+ self.win_sum += cur_frame_state
647
+ self.win_state[self.cur_win_pos] = cur_frame_state
648
+ self.cur_win_pos = (self.cur_win_pos + 1) % self.win_size_frame
649
+
650
+ if (
651
+ self.pre_frame_state == FrameState.kFrameStateSil
652
+ and self.win_sum >= self.sil_to_speech_frmcnt_thres
653
+ ):
654
+ self.pre_frame_state = FrameState.kFrameStateSpeech
655
+ return AudioChangeState.kChangeStateSil2Speech
656
+
657
+ if (
658
+ self.pre_frame_state == FrameState.kFrameStateSpeech
659
+ and self.win_sum <= self.speech_to_sil_frmcnt_thres
660
+ ):
661
+ self.pre_frame_state = FrameState.kFrameStateSil
662
+ return AudioChangeState.kChangeStateSpeech2Sil
663
+
664
+ if self.pre_frame_state == FrameState.kFrameStateSil:
665
+ return AudioChangeState.kChangeStateSil2Sil
666
+ if self.pre_frame_state == FrameState.kFrameStateSpeech:
667
+ return AudioChangeState.kChangeStateSpeech2Speech
668
+ return AudioChangeState.kChangeStateInvalid
669
+
670
+ def frame_size_ms(self) -> int:
671
+ return int(self.frame_size_ms)
672
+
673
+
674
+ class E2EVadModel:
675
+ def __init__(self, config, vad_post_args: Dict[str, Any], root_dir: Path):
676
+ super(E2EVadModel, self).__init__()
677
+ self.vad_opts = VADXOptions(**vad_post_args)
678
+ self.windows_detector = WindowDetector(
679
+ self.vad_opts.window_size_ms,
680
+ self.vad_opts.sil_to_speech_time_thres,
681
+ self.vad_opts.speech_to_sil_time_thres,
682
+ self.vad_opts.frame_in_ms,
683
+ )
684
+ self.model = VadOrtInferRuntimeSession(config, root_dir)
685
+ self.all_reset_detection()
686
+
687
+ def all_reset_detection(self):
688
+ # init variables
689
+ self.is_final = False
690
+ self.data_buf_start_frame = 0
691
+ self.frm_cnt = 0
692
+ self.latest_confirmed_speech_frame = 0
693
+ self.lastest_confirmed_silence_frame = -1
694
+ self.continous_silence_frame_count = 0
695
+ self.vad_state_machine = VadStateMachine.kVadInStateStartPointNotDetected
696
+ self.confirmed_start_frame = -1
697
+ self.confirmed_end_frame = -1
698
+ self.number_end_time_detected = 0
699
+ self.sil_frame = 0
700
+ self.sil_pdf_ids = self.vad_opts.sil_pdf_ids
701
+ self.noise_average_decibel = -100.0
702
+ self.pre_end_silence_detected = False
703
+ self.next_seg = True
704
+
705
+ self.output_data_buf = []
706
+ self.output_data_buf_offset = 0
707
+ self.frame_probs = []
708
+ self.max_end_sil_frame_cnt_thresh = (
709
+ self.vad_opts.max_end_silence_time - self.vad_opts.speech_to_sil_time_thres
710
+ )
711
+ self.speech_noise_thres = self.vad_opts.speech_noise_thres
712
+ self.scores = None
713
+ self.scores_offset = 0
714
+ self.max_time_out = False
715
+ self.decibel = []
716
+ self.decibel_offset = 0
717
+ self.data_buf_size = 0
718
+ self.data_buf_all_size = 0
719
+ self.waveform = None
720
+ self.reset_detection()
721
+
722
+ def reset_detection(self):
723
+ self.continous_silence_frame_count = 0
724
+ self.latest_confirmed_speech_frame = 0
725
+ self.lastest_confirmed_silence_frame = -1
726
+ self.confirmed_start_frame = -1
727
+ self.confirmed_end_frame = -1
728
+ self.vad_state_machine = VadStateMachine.kVadInStateStartPointNotDetected
729
+ self.windows_detector.reset()
730
+ self.sil_frame = 0
731
+ self.frame_probs = []
732
+
733
+ def compute_decibel(self) -> None:
734
+ frame_sample_length = int(
735
+ self.vad_opts.frame_length_ms * self.vad_opts.sample_rate / 1000
736
+ )
737
+ frame_shift_length = int(
738
+ self.vad_opts.frame_in_ms * self.vad_opts.sample_rate / 1000
739
+ )
740
+ if self.data_buf_all_size == 0:
741
+ self.data_buf_all_size = len(self.waveform[0])
742
+ self.data_buf_size = self.data_buf_all_size
743
+ else:
744
+ self.data_buf_all_size += len(self.waveform[0])
745
+
746
+ for offset in range(
747
+ 0, self.waveform.shape[1] - frame_sample_length + 1, frame_shift_length
748
+ ):
749
+ self.decibel.append(
750
+ 10
751
+ * np.log10(
752
+ np.square(
753
+ self.waveform[0][offset : offset + frame_sample_length]
754
+ ).sum()
755
+ + 1e-6
756
+ )
757
+ )
758
+
759
+ def compute_scores(self, feats: np.ndarray) -> None:
760
+ scores = self.model(feats)
761
+ self.vad_opts.nn_eval_block_size = scores[0].shape[1]
762
+ self.frm_cnt += scores[0].shape[1] # count total frames
763
+ if isinstance(feats, list):
764
+ # return B * T * D
765
+ feats = feats[0]
766
+
767
+ assert (
768
+ scores[0].shape[1] == feats.shape[1]
769
+ ), "The shape between feats and scores does not match"
770
+
771
+ self.scores = scores[0] # the first calculation
772
+ self.scores_offset += self.scores.shape[1]
773
+
774
+ return scores[1:]
775
+
776
+ def pop_data_buf_till_frame(self, frame_idx: int) -> None: # need check again
777
+ while self.data_buf_start_frame < frame_idx:
778
+ if self.data_buf_size >= int(
779
+ self.vad_opts.frame_in_ms * self.vad_opts.sample_rate / 1000
780
+ ):
781
+ self.data_buf_start_frame += 1
782
+ self.data_buf_size = (
783
+ self.data_buf_all_size
784
+ - self.data_buf_start_frame
785
+ * int(self.vad_opts.frame_in_ms * self.vad_opts.sample_rate / 1000)
786
+ )
787
+
788
+ def pop_data_to_output_buf(
789
+ self,
790
+ start_frm: int,
791
+ frm_cnt: int,
792
+ first_frm_is_start_point: bool,
793
+ last_frm_is_end_point: bool,
794
+ end_point_is_sent_end: bool,
795
+ ) -> None:
796
+ self.pop_data_buf_till_frame(start_frm)
797
+ expected_sample_number = int(
798
+ frm_cnt * self.vad_opts.sample_rate * self.vad_opts.frame_in_ms / 1000
799
+ )
800
+ if last_frm_is_end_point:
801
+ extra_sample = max(
802
+ 0,
803
+ int(
804
+ self.vad_opts.frame_length_ms * self.vad_opts.sample_rate / 1000
805
+ - self.vad_opts.sample_rate * self.vad_opts.frame_in_ms / 1000
806
+ ),
807
+ )
808
+ expected_sample_number += int(extra_sample)
809
+ if end_point_is_sent_end:
810
+ expected_sample_number = max(expected_sample_number, self.data_buf_size)
811
+ if self.data_buf_size < expected_sample_number:
812
+ logging.error("error in calling pop data_buf\n")
813
+
814
+ if len(self.output_data_buf) == 0 or first_frm_is_start_point:
815
+ self.output_data_buf.append(E2EVadSpeechBufWithDoa())
816
+ self.output_data_buf[-1].reset()
817
+ self.output_data_buf[-1].start_ms = start_frm * self.vad_opts.frame_in_ms
818
+ self.output_data_buf[-1].end_ms = self.output_data_buf[-1].start_ms
819
+ self.output_data_buf[-1].doa = 0
820
+ cur_seg = self.output_data_buf[-1]
821
+ if cur_seg.end_ms != start_frm * self.vad_opts.frame_in_ms:
822
+ logging.error("warning\n")
823
+ out_pos = len(cur_seg.buffer) # cur_seg.buff现在没做任何操作
824
+ data_to_pop = 0
825
+ if end_point_is_sent_end:
826
+ data_to_pop = expected_sample_number
827
+ else:
828
+ data_to_pop = int(
829
+ frm_cnt * self.vad_opts.frame_in_ms * self.vad_opts.sample_rate / 1000
830
+ )
831
+ if data_to_pop > self.data_buf_size:
832
+ logging.error("VAD data_to_pop is bigger than self.data_buf.size()!!!\n")
833
+ data_to_pop = self.data_buf_size
834
+ expected_sample_number = self.data_buf_size
835
+
836
+ cur_seg.doa = 0
837
+ for sample_cpy_out in range(0, data_to_pop):
838
+ # cur_seg.buffer[out_pos ++] = data_buf_.back();
839
+ out_pos += 1
840
+ for sample_cpy_out in range(data_to_pop, expected_sample_number):
841
+ # cur_seg.buffer[out_pos++] = data_buf_.back()
842
+ out_pos += 1
843
+ if cur_seg.end_ms != start_frm * self.vad_opts.frame_in_ms:
844
+ logging.error("Something wrong with the VAD algorithm\n")
845
+ self.data_buf_start_frame += frm_cnt
846
+ cur_seg.end_ms = (start_frm + frm_cnt) * self.vad_opts.frame_in_ms
847
+ if first_frm_is_start_point:
848
+ cur_seg.contain_seg_start_point = True
849
+ if last_frm_is_end_point:
850
+ cur_seg.contain_seg_end_point = True
851
+
852
+ def on_silence_detected(self, valid_frame: int):
853
+ self.lastest_confirmed_silence_frame = valid_frame
854
+ if self.vad_state_machine == VadStateMachine.kVadInStateStartPointNotDetected:
855
+ self.pop_data_buf_till_frame(valid_frame)
856
+ # silence_detected_callback_
857
+ # pass
858
+
859
+ def on_voice_detected(self, valid_frame: int) -> None:
860
+ self.latest_confirmed_speech_frame = valid_frame
861
+ self.pop_data_to_output_buf(valid_frame, 1, False, False, False)
862
+
863
+ def on_voice_start(self, start_frame: int, fake_result: bool = False) -> None:
864
+ if self.vad_opts.do_start_point_detection:
865
+ pass
866
+ if self.confirmed_start_frame != -1:
867
+ logging.error("not reset vad properly\n")
868
+ else:
869
+ self.confirmed_start_frame = start_frame
870
+
871
+ if (
872
+ not fake_result
873
+ and self.vad_state_machine
874
+ == VadStateMachine.kVadInStateStartPointNotDetected
875
+ ):
876
+ self.pop_data_to_output_buf(
877
+ self.confirmed_start_frame, 1, True, False, False
878
+ )
879
+
880
+ def on_voice_end(
881
+ self, end_frame: int, fake_result: bool, is_last_frame: bool
882
+ ) -> None:
883
+ for t in range(self.latest_confirmed_speech_frame + 1, end_frame):
884
+ self.on_voice_detected(t)
885
+ if self.vad_opts.do_end_point_detection:
886
+ pass
887
+ if self.confirmed_end_frame != -1:
888
+ logging.error("not reset vad properly\n")
889
+ else:
890
+ self.confirmed_end_frame = end_frame
891
+ if not fake_result:
892
+ self.sil_frame = 0
893
+ self.pop_data_to_output_buf(
894
+ self.confirmed_end_frame, 1, False, True, is_last_frame
895
+ )
896
+ self.number_end_time_detected += 1
897
+
898
+ def maybe_on_voice_end_last_frame(
899
+ self, is_final_frame: bool, cur_frm_idx: int
900
+ ) -> None:
901
+ if is_final_frame:
902
+ self.on_voice_end(cur_frm_idx, False, True)
903
+ self.vad_state_machine = VadStateMachine.kVadInStateEndPointDetected
904
+
905
+ def get_latency(self) -> int:
906
+ return int(self.latency_frm_num_at_start_point() * self.vad_opts.frame_in_ms)
907
+
908
+ def latency_frm_num_at_start_point(self) -> int:
909
+ vad_latency = self.windows_detector.get_win_size()
910
+ if self.vad_opts.do_extend:
911
+ vad_latency += int(
912
+ self.vad_opts.lookback_time_start_point / self.vad_opts.frame_in_ms
913
+ )
914
+ return vad_latency
915
+
916
+ def get_frame_state(self, t: int) -> FrameState:
917
+ frame_state = FrameState.kFrameStateInvalid
918
+ cur_decibel = self.decibel[t - self.decibel_offset]
919
+ cur_snr = cur_decibel - self.noise_average_decibel
920
+ # for each frame, calc log posterior probability of each state
921
+ if cur_decibel < self.vad_opts.decibel_thres:
922
+ frame_state = FrameState.kFrameStateSil
923
+ self.detect_one_frame(frame_state, t, False)
924
+ return frame_state
925
+
926
+ sum_score = 0.0
927
+ noise_prob = 0.0
928
+ assert len(self.sil_pdf_ids) == self.vad_opts.silence_pdf_num
929
+ if len(self.sil_pdf_ids) > 0:
930
+ assert len(self.scores) == 1 # 只支持batch_size = 1的测试
931
+ sil_pdf_scores = [
932
+ self.scores[0][t - self.scores_offset][sil_pdf_id]
933
+ for sil_pdf_id in self.sil_pdf_ids
934
+ ]
935
+ sum_score = sum(sil_pdf_scores)
936
+ noise_prob = math.log(sum_score) * self.vad_opts.speech_2_noise_ratio
937
+ total_score = 1.0
938
+ sum_score = total_score - sum_score
939
+ speech_prob = math.log(sum_score)
940
+ if self.vad_opts.output_frame_probs:
941
+ frame_prob = E2EVadFrameProb()
942
+ frame_prob.noise_prob = noise_prob
943
+ frame_prob.speech_prob = speech_prob
944
+ frame_prob.score = sum_score
945
+ frame_prob.frame_id = t
946
+ self.frame_probs.append(frame_prob)
947
+ if math.exp(speech_prob) >= math.exp(noise_prob) + self.speech_noise_thres:
948
+ if (
949
+ cur_snr >= self.vad_opts.snr_thres
950
+ and cur_decibel >= self.vad_opts.decibel_thres
951
+ ):
952
+ frame_state = FrameState.kFrameStateSpeech
953
+ else:
954
+ frame_state = FrameState.kFrameStateSil
955
+ else:
956
+ frame_state = FrameState.kFrameStateSil
957
+ if self.noise_average_decibel < -99.9:
958
+ self.noise_average_decibel = cur_decibel
959
+ else:
960
+ self.noise_average_decibel = (
961
+ cur_decibel
962
+ + self.noise_average_decibel
963
+ * (self.vad_opts.noise_frame_num_used_for_snr - 1)
964
+ ) / self.vad_opts.noise_frame_num_used_for_snr
965
+
966
+ return frame_state
967
+
968
+ def infer_offline(
969
+ self,
970
+ feats: np.ndarray,
971
+ waveform: np.ndarray,
972
+ in_cache: Dict[str, np.ndarray] = dict(),
973
+ is_final: bool = False,
974
+ ) -> Tuple[List[List[List[int]]], Dict[str, np.ndarray]]:
975
+ self.waveform = waveform
976
+ self.compute_decibel()
977
+
978
+ self.compute_scores(feats)
979
+ if not is_final:
980
+ self.detect_common_frames()
981
+ else:
982
+ self.detect_last_frames()
983
+ segments = []
984
+ for batch_num in range(0, feats.shape[0]): # only support batch_size = 1 now
985
+ segment_batch = []
986
+ if len(self.output_data_buf) > 0:
987
+ for i in range(self.output_data_buf_offset, len(self.output_data_buf)):
988
+ if (
989
+ not self.output_data_buf[i].contain_seg_start_point
990
+ or not self.output_data_buf[i].contain_seg_end_point
991
+ ):
992
+ continue
993
+ segment = [
994
+ self.output_data_buf[i].start_ms,
995
+ self.output_data_buf[i].end_ms,
996
+ ]
997
+ segment_batch.append(segment)
998
+ self.output_data_buf_offset += 1 # need update this parameter
999
+ if segment_batch:
1000
+ segments.append(segment_batch)
1001
+
1002
+ if is_final:
1003
+ # reset class variables and clear the dict for the next query
1004
+ self.all_reset_detection()
1005
+ return segments, in_cache
1006
+
1007
+ def infer_online(
1008
+ self,
1009
+ feats: np.ndarray,
1010
+ waveform: np.ndarray,
1011
+ in_cache: list = None,
1012
+ is_final: bool = False,
1013
+ max_end_sil: int = 800,
1014
+ ) -> Tuple[List[List[List[int]]], Dict[str, np.ndarray]]:
1015
+ feats = [feats]
1016
+ if in_cache is None:
1017
+ in_cache = []
1018
+
1019
+ self.max_end_sil_frame_cnt_thresh = (
1020
+ max_end_sil - self.vad_opts.speech_to_sil_time_thres
1021
+ )
1022
+ self.waveform = waveform # compute decibel for each frame
1023
+ feats.extend(in_cache)
1024
+ in_cache = self.compute_scores(feats)
1025
+ self.compute_decibel()
1026
+
1027
+ if is_final:
1028
+ self.detect_last_frames()
1029
+ else:
1030
+ self.detect_common_frames()
1031
+
1032
+ segments = []
1033
+ # only support batch_size = 1 now
1034
+ for batch_num in range(0, feats[0].shape[0]):
1035
+ if len(self.output_data_buf) > 0:
1036
+ for i in range(self.output_data_buf_offset, len(self.output_data_buf)):
1037
+ if not self.output_data_buf[i].contain_seg_start_point:
1038
+ continue
1039
+ if (
1040
+ not self.next_seg
1041
+ and not self.output_data_buf[i].contain_seg_end_point
1042
+ ):
1043
+ continue
1044
+ start_ms = self.output_data_buf[i].start_ms if self.next_seg else -1
1045
+ if self.output_data_buf[i].contain_seg_end_point:
1046
+ end_ms = self.output_data_buf[i].end_ms
1047
+ self.next_seg = True
1048
+ self.output_data_buf_offset += 1
1049
+ else:
1050
+ end_ms = -1
1051
+ self.next_seg = False
1052
+ segments.append([start_ms, end_ms])
1053
+
1054
+ return segments, in_cache
1055
+
1056
+ def get_frames_state(
1057
+ self,
1058
+ feats: np.ndarray,
1059
+ waveform: np.ndarray,
1060
+ in_cache: list = None,
1061
+ is_final: bool = False,
1062
+ max_end_sil: int = 800,
1063
+ ):
1064
+ feats = [feats]
1065
+ states = []
1066
+ if in_cache is None:
1067
+ in_cache = []
1068
+
1069
+ self.max_end_sil_frame_cnt_thresh = (
1070
+ max_end_sil - self.vad_opts.speech_to_sil_time_thres
1071
+ )
1072
+ self.waveform = waveform # compute decibel for each frame
1073
+ feats.extend(in_cache)
1074
+ in_cache = self.compute_scores(feats)
1075
+ self.compute_decibel()
1076
+
1077
+ if self.vad_state_machine == VadStateMachine.kVadInStateEndPointDetected:
1078
+ return states
1079
+
1080
+ for i in range(self.vad_opts.nn_eval_block_size - 1, -1, -1):
1081
+ frame_state = FrameState.kFrameStateInvalid
1082
+ frame_state = self.get_frame_state(self.frm_cnt - 1 - i)
1083
+ states.append(frame_state)
1084
+ if i == 0 and is_final:
1085
+ logging.info("last frame detected")
1086
+ self.detect_one_frame(frame_state, self.frm_cnt - 1, True)
1087
+ else:
1088
+ self.detect_one_frame(frame_state, self.frm_cnt - 1 - i, False)
1089
+
1090
+ return states
1091
+
1092
+ def detect_common_frames(self) -> int:
1093
+ if self.vad_state_machine == VadStateMachine.kVadInStateEndPointDetected:
1094
+ return 0
1095
+ for i in range(self.vad_opts.nn_eval_block_size - 1, -1, -1):
1096
+ frame_state = FrameState.kFrameStateInvalid
1097
+ frame_state = self.get_frame_state(self.frm_cnt - 1 - i)
1098
+ # print(f"cur frame: {self.frm_cnt - 1 - i}, state is {frame_state}")
1099
+ self.detect_one_frame(frame_state, self.frm_cnt - 1 - i, False)
1100
+
1101
+ self.decibel = self.decibel[self.vad_opts.nn_eval_block_size - 1 :]
1102
+ self.decibel_offset = self.frm_cnt - 1 - i
1103
+ return 0
1104
+
1105
+ def detect_last_frames(self) -> int:
1106
+ if self.vad_state_machine == VadStateMachine.kVadInStateEndPointDetected:
1107
+ return 0
1108
+ for i in range(self.vad_opts.nn_eval_block_size - 1, -1, -1):
1109
+ frame_state = FrameState.kFrameStateInvalid
1110
+ frame_state = self.get_frame_state(self.frm_cnt - 1 - i)
1111
+ if i != 0:
1112
+ self.detect_one_frame(frame_state, self.frm_cnt - 1 - i, False)
1113
+ else:
1114
+ self.detect_one_frame(frame_state, self.frm_cnt - 1, True)
1115
+
1116
+ return 0
1117
+
1118
+ def detect_one_frame(
1119
+ self, cur_frm_state: FrameState, cur_frm_idx: int, is_final_frame: bool
1120
+ ) -> None:
1121
+ tmp_cur_frm_state = FrameState.kFrameStateInvalid
1122
+ if cur_frm_state == FrameState.kFrameStateSpeech:
1123
+ if math.fabs(1.0) > float(self.vad_opts.fe_prior_thres):
1124
+ tmp_cur_frm_state = FrameState.kFrameStateSpeech
1125
+ else:
1126
+ tmp_cur_frm_state = FrameState.kFrameStateSil
1127
+ elif cur_frm_state == FrameState.kFrameStateSil:
1128
+ tmp_cur_frm_state = FrameState.kFrameStateSil
1129
+ state_change = self.windows_detector.detect_one_frame(
1130
+ tmp_cur_frm_state, cur_frm_idx
1131
+ )
1132
+ frm_shift_in_ms = self.vad_opts.frame_in_ms
1133
+ if AudioChangeState.kChangeStateSil2Speech == state_change:
1134
+ self.continous_silence_frame_count = 0
1135
+ self.pre_end_silence_detected = False
1136
+
1137
+ if (
1138
+ self.vad_state_machine
1139
+ == VadStateMachine.kVadInStateStartPointNotDetected
1140
+ ):
1141
+ start_frame = max(
1142
+ self.data_buf_start_frame,
1143
+ cur_frm_idx - self.latency_frm_num_at_start_point(),
1144
+ )
1145
+ self.on_voice_start(start_frame)
1146
+ self.vad_state_machine = VadStateMachine.kVadInStateInSpeechSegment
1147
+ for t in range(start_frame + 1, cur_frm_idx + 1):
1148
+ self.on_voice_detected(t)
1149
+ elif self.vad_state_machine == VadStateMachine.kVadInStateInSpeechSegment:
1150
+ for t in range(self.latest_confirmed_speech_frame + 1, cur_frm_idx):
1151
+ self.on_voice_detected(t)
1152
+ if (
1153
+ cur_frm_idx - self.confirmed_start_frame + 1
1154
+ > self.vad_opts.max_single_segment_time / frm_shift_in_ms
1155
+ ):
1156
+ self.on_voice_end(cur_frm_idx, False, False)
1157
+ self.vad_state_machine = VadStateMachine.kVadInStateEndPointDetected
1158
+ elif not is_final_frame:
1159
+ self.on_voice_detected(cur_frm_idx)
1160
+ else:
1161
+ self.maybe_on_voice_end_last_frame(is_final_frame, cur_frm_idx)
1162
+ else:
1163
+ pass
1164
+ elif AudioChangeState.kChangeStateSpeech2Sil == state_change:
1165
+ self.continous_silence_frame_count = 0
1166
+ if (
1167
+ self.vad_state_machine
1168
+ == VadStateMachine.kVadInStateStartPointNotDetected
1169
+ ):
1170
+ pass
1171
+ elif self.vad_state_machine == VadStateMachine.kVadInStateInSpeechSegment:
1172
+ if (
1173
+ cur_frm_idx - self.confirmed_start_frame + 1
1174
+ > self.vad_opts.max_single_segment_time / frm_shift_in_ms
1175
+ ):
1176
+ self.on_voice_end(cur_frm_idx, False, False)
1177
+ self.vad_state_machine = VadStateMachine.kVadInStateEndPointDetected
1178
+ elif not is_final_frame:
1179
+ self.on_voice_detected(cur_frm_idx)
1180
+ else:
1181
+ self.maybe_on_voice_end_last_frame(is_final_frame, cur_frm_idx)
1182
+ else:
1183
+ pass
1184
+ elif AudioChangeState.kChangeStateSpeech2Speech == state_change:
1185
+ self.continous_silence_frame_count = 0
1186
+ if self.vad_state_machine == VadStateMachine.kVadInStateInSpeechSegment:
1187
+ if (
1188
+ cur_frm_idx - self.confirmed_start_frame + 1
1189
+ > self.vad_opts.max_single_segment_time / frm_shift_in_ms
1190
+ ):
1191
+ self.max_time_out = True
1192
+ self.on_voice_end(cur_frm_idx, False, False)
1193
+ self.vad_state_machine = VadStateMachine.kVadInStateEndPointDetected
1194
+ elif not is_final_frame:
1195
+ self.on_voice_detected(cur_frm_idx)
1196
+ else:
1197
+ self.maybe_on_voice_end_last_frame(is_final_frame, cur_frm_idx)
1198
+ else:
1199
+ pass
1200
+ elif AudioChangeState.kChangeStateSil2Sil == state_change:
1201
+ self.continous_silence_frame_count += 1
1202
+ if (
1203
+ self.vad_state_machine
1204
+ == VadStateMachine.kVadInStateStartPointNotDetected
1205
+ ):
1206
+ # silence timeout, return zero length decision
1207
+ if (
1208
+ (
1209
+ self.vad_opts.detect_mode
1210
+ == VadDetectMode.kVadSingleUtteranceDetectMode.value
1211
+ )
1212
+ and (
1213
+ self.continous_silence_frame_count * frm_shift_in_ms
1214
+ > self.vad_opts.max_start_silence_time
1215
+ )
1216
+ ) or (is_final_frame and self.number_end_time_detected == 0):
1217
+ for t in range(
1218
+ self.lastest_confirmed_silence_frame + 1, cur_frm_idx
1219
+ ):
1220
+ self.on_silence_detected(t)
1221
+ self.on_voice_start(0, True)
1222
+ self.on_voice_end(0, True, False)
1223
+ self.vad_state_machine = VadStateMachine.kVadInStateEndPointDetected
1224
+ else:
1225
+ if cur_frm_idx >= self.latency_frm_num_at_start_point():
1226
+ self.on_silence_detected(
1227
+ cur_frm_idx - self.latency_frm_num_at_start_point()
1228
+ )
1229
+ elif self.vad_state_machine == VadStateMachine.kVadInStateInSpeechSegment:
1230
+ if (
1231
+ self.continous_silence_frame_count * frm_shift_in_ms
1232
+ >= self.max_end_sil_frame_cnt_thresh
1233
+ ):
1234
+ lookback_frame = int(
1235
+ self.max_end_sil_frame_cnt_thresh / frm_shift_in_ms
1236
+ )
1237
+ if self.vad_opts.do_extend:
1238
+ lookback_frame -= int(
1239
+ self.vad_opts.lookahead_time_end_point / frm_shift_in_ms
1240
+ )
1241
+ lookback_frame -= 1
1242
+ lookback_frame = max(0, lookback_frame)
1243
+ self.on_voice_end(cur_frm_idx - lookback_frame, False, False)
1244
+ self.vad_state_machine = VadStateMachine.kVadInStateEndPointDetected
1245
+ elif (
1246
+ cur_frm_idx - self.confirmed_start_frame + 1
1247
+ > self.vad_opts.max_single_segment_time / frm_shift_in_ms
1248
+ ):
1249
+ self.on_voice_end(cur_frm_idx, False, False)
1250
+ self.vad_state_machine = VadStateMachine.kVadInStateEndPointDetected
1251
+ elif self.vad_opts.do_extend and not is_final_frame:
1252
+ if self.continous_silence_frame_count <= int(
1253
+ self.vad_opts.lookahead_time_end_point / frm_shift_in_ms
1254
+ ):
1255
+ self.on_voice_detected(cur_frm_idx)
1256
+ else:
1257
+ self.maybe_on_voice_end_last_frame(is_final_frame, cur_frm_idx)
1258
+ else:
1259
+ pass
1260
+
1261
+ if (
1262
+ self.vad_state_machine == VadStateMachine.kVadInStateEndPointDetected
1263
+ and self.vad_opts.detect_mode
1264
+ == VadDetectMode.kVadMutipleUtteranceDetectMode.value
1265
+ ):
1266
+ self.reset_detection()
1267
+
1268
+
1269
+ class FSMNVad(object):
1270
+ def __init__(self, config_dir: str):
1271
+ config_dir = Path(config_dir)
1272
+ self.config = read_yaml(config_dir / "fsmn-config.yaml")
1273
+ self.frontend = WavFrontend(
1274
+ cmvn_file=config_dir / "fsmn-am.mvn",
1275
+ **self.config["WavFrontend"]["frontend_conf"],
1276
+ )
1277
+ self.config["FSMN"]["model_path"] = config_dir / "fsmnvad-offline.onnx"
1278
+
1279
+ self.vad = E2EVadModel(
1280
+ self.config["FSMN"], self.config["vadPostArgs"], config_dir
1281
+ )
1282
+
1283
+ def set_parameters(self, mode):
1284
+ pass
1285
+
1286
+ def extract_feature(self, waveform):
1287
+ fbank, _ = self.frontend.fbank(waveform)
1288
+ feats, feats_len = self.frontend.lfr_cmvn(fbank)
1289
+ return feats.astype(np.float32), feats_len
1290
+
1291
+ def is_speech(self, buf, sample_rate=16000):
1292
+ assert sample_rate == 16000, "only support 16k sample rate"
1293
+
1294
+ def segments_offline(self, waveform_path: Union[str, Path, np.ndarray]):
1295
+ """get sements of audio"""
1296
+
1297
+ if isinstance(waveform_path, np.ndarray):
1298
+ waveform = waveform_path
1299
+ else:
1300
+ if not os.path.exists(waveform_path):
1301
+ raise FileExistsError(f"{waveform_path} is not exist.")
1302
+ if os.path.isfile(waveform_path):
1303
+ logging.info(f"load audio {waveform_path}")
1304
+ waveform, _sample_rate = sf.read(
1305
+ waveform_path,
1306
+ dtype="float32",
1307
+ )
1308
+ else:
1309
+ raise FileNotFoundError(str(Path))
1310
+ assert (
1311
+ _sample_rate == 16000
1312
+ ), f"only support 16k sample rate, current sample rate is {_sample_rate}"
1313
+
1314
+ feats, feats_len = self.extract_feature(waveform)
1315
+ waveform = waveform[None, ...]
1316
+ segments_part, in_cache = self.vad.infer_offline(
1317
+ feats[None, ...], waveform, is_final=True
1318
+ )
1319
+ return segments_part[0]
1320
+
1321
+ # ```
1322
+
1323
+ # File: sense_voice.py
1324
+ # ```py
1325
+ # -*- coding:utf-8 -*-
1326
+ # @FileName :sense_voice.py.py
1327
+ # @Time :2024/7/18 15:40
1328
+ # @Author :lovemefan
1329
+ # @Email :lovemefan@outlook.com
1330
+
1331
+ languages = {"auto": 0, "zh": 3, "en": 4, "yue": 7, "ja": 11, "ko": 12, "nospeech": 13}
1332
+ formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
1333
+ logging.basicConfig(format=formatter, level=logging.INFO)
1334
+
1335
+ def main():
1336
+ arg_parser = argparse.ArgumentParser(description="Sense Voice")
1337
+ arg_parser.add_argument("-a", "--audio_file", required=True, type=str, help="Model")
1338
+ download_model_path = os.path.dirname(__file__)
1339
+ arg_parser.add_argument(
1340
+ "-dp",
1341
+ "--download_path",
1342
+ default=download_model_path,
1343
+ type=str,
1344
+ help="dir path of resource downloaded",
1345
+ )
1346
+ arg_parser.add_argument("-d", "--device", default=-1, type=int, help="Device")
1347
+ arg_parser.add_argument(
1348
+ "-n", "--num_threads", default=4, type=int, help="Num threads"
1349
+ )
1350
+ arg_parser.add_argument(
1351
+ "-l",
1352
+ "--language",
1353
+ choices=languages.keys(),
1354
+ default="auto",
1355
+ type=str,
1356
+ help="Language",
1357
+ )
1358
+ arg_parser.add_argument("--use_itn", action="store_true", help="Use ITN")
1359
+ args = arg_parser.parse_args()
1360
+
1361
+ front = WavFrontend(os.path.join(download_model_path, "am.mvn"))
1362
+
1363
+ model = SenseVoiceInferenceSession(
1364
+ os.path.join(download_model_path, "embedding.npy"),
1365
+ os.path.join(
1366
+ download_model_path,
1367
+ "sense-voice-encoder.rknn",
1368
+ ),
1369
+ os.path.join(download_model_path, "chn_jpn_yue_eng_ko_spectok.bpe.model"),
1370
+ args.device,
1371
+ args.num_threads,
1372
+ )
1373
+ waveform, _sample_rate = sf.read(
1374
+ args.audio_file,
1375
+ dtype="float32",
1376
+ always_2d=True
1377
+ )
1378
+
1379
+ logging.info(f"Audio {args.audio_file} is {len(waveform) / _sample_rate} seconds, {waveform.shape[1]} channel")
1380
+ # load vad model
1381
+ start = time.time()
1382
+ vad = FSMNVad(download_model_path)
1383
+ for channel_id, channel_data in enumerate(waveform.T):
1384
+ segments = vad.segments_offline(channel_data)
1385
+ results = ""
1386
+ for part in segments:
1387
+ audio_feats = front.get_features(channel_data[part[0] * 16 : part[1] * 16])
1388
+ asr_result = model(
1389
+ audio_feats[None, ...],
1390
+ language=languages[args.language],
1391
+ use_itn=args.use_itn,
1392
+ )
1393
+ logging.info(f"[Channel {channel_id}] [{part[0] / 1000}s - {part[1] / 1000}s] {asr_result}")
1394
+ vad.vad.all_reset_detection()
1395
+ decoding_time = time.time() - start
1396
+ logging.info(f"Decoder audio takes {decoding_time} seconds")
1397
+ logging.info(f"The RTF is {decoding_time/(waveform.shape[1] * len(waveform) / _sample_rate)}.")
1398
+
1399
+
1400
+ if __name__ == "__main__":
1401
+ main()
1402
+