align with silero-vad-axera 0.1.2 (axmodel-only)
Browse files- README.md +15 -12
- StreamVAD.py +26 -20
- main.py +5 -5
- requirements.txt +5 -6
README.md
CHANGED
|
@@ -6,22 +6,25 @@ license: mit
|
|
| 6 |
流式语音端点识别
|
| 7 |
|
| 8 |
|
| 9 |
-
## 安装依赖
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
| 16 |
|
| 17 |
## Demo
|
| 18 |
|
| 19 |
### CLI
|
| 20 |
|
| 21 |
-
```
|
| 22 |
-
python main.py --input demo.wav --
|
| 23 |
-
|
| 24 |
-
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
### Gradio
|
|
@@ -63,4 +66,4 @@ result的格式为:
|
|
| 63 |
}
|
| 64 |
```
|
| 65 |
|
| 66 |
-
时间戳的格式可通过StreamVAD.datetime_format设置
|
|
|
|
| 6 |
流式语音端点识别
|
| 7 |
|
| 8 |
|
| 9 |
+
## 安装依赖
|
| 10 |
+
|
| 11 |
+
本仓库只支持 **axmodel(NPU)推理**(AX650 / AX630C),不再支持 onnx CPU 后端。
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
sudo apt install libsndfile1
|
| 15 |
+
|
| 16 |
+
pip install -r requirements.txt
|
| 17 |
+
```
|
| 18 |
|
| 19 |
## Demo
|
| 20 |
|
| 21 |
### CLI
|
| 22 |
|
| 23 |
+
```
|
| 24 |
+
python main.py --input demo.wav --backend ax650 --output_dir output # AX650 板
|
| 25 |
+
python main.py --input demo.wav --backend ax630c --output_dir output # AX620E/AX630C 板
|
| 26 |
+
```
|
| 27 |
+
被分段的语音后保存在output目录中
|
| 28 |
|
| 29 |
|
| 30 |
### Gradio
|
|
|
|
| 66 |
}
|
| 67 |
```
|
| 68 |
|
| 69 |
+
时间戳的格式可通过StreamVAD.datetime_format设置
|
StreamVAD.py
CHANGED
|
@@ -3,7 +3,7 @@ import numpy as np
|
|
| 3 |
from datetime import datetime, timedelta
|
| 4 |
|
| 5 |
|
| 6 |
-
class StreamVAD:
|
| 7 |
def __init__(self, backend='ax650',
|
| 8 |
sensitivity=0.5,
|
| 9 |
silence_ms=200,
|
|
@@ -17,10 +17,13 @@ class StreamVAD:
|
|
| 17 |
datetime_format: format of datetime in return data
|
| 18 |
'''
|
| 19 |
|
| 20 |
-
self.model = load_silero_vad(backend)
|
| 21 |
-
|
| 22 |
-
self.
|
| 23 |
-
self.
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
self.reset()
|
| 26 |
|
|
@@ -37,19 +40,22 @@ class StreamVAD:
|
|
| 37 |
self.model.reset_states()
|
| 38 |
|
| 39 |
|
| 40 |
-
def run(self, audio: np.ndarray, sr: int = 16000):
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
# is speech
|
| 55 |
if prob > 1 - self.sensitivity:
|
|
@@ -66,7 +72,7 @@ class StreamVAD:
|
|
| 66 |
self.silence_count += 1
|
| 67 |
|
| 68 |
# exceed silence limit
|
| 69 |
-
if 1000 * self.silence_count * self.
|
| 70 |
# return audio segment
|
| 71 |
self.return_data['end_ts'] = ts
|
| 72 |
self.return_data['audio'] = np.concatenate(self.vad_data_list, axis=-1)
|
|
@@ -78,4 +84,4 @@ class StreamVAD:
|
|
| 78 |
self.vad_data_list.append(audio_slice)
|
| 79 |
|
| 80 |
# timestamp
|
| 81 |
-
cur_ts += timedelta(seconds=self.
|
|
|
|
| 3 |
from datetime import datetime, timedelta
|
| 4 |
|
| 5 |
|
| 6 |
+
class StreamVAD:
|
| 7 |
def __init__(self, backend='ax650',
|
| 8 |
sensitivity=0.5,
|
| 9 |
silence_ms=200,
|
|
|
|
| 17 |
datetime_format: format of datetime in return data
|
| 18 |
'''
|
| 19 |
|
| 20 |
+
self.model = load_silero_vad(backend)
|
| 21 |
+
# axmodel 为 16k 静态图:采样率 16000、每帧 512 样本
|
| 22 |
+
self.sr = self.model.sample_rates[0]
|
| 23 |
+
self.num_samples = 512
|
| 24 |
+
self.sensitivity = sensitivity
|
| 25 |
+
self.silence_ms = silence_ms
|
| 26 |
+
self.datetime_format = datetime_format
|
| 27 |
|
| 28 |
self.reset()
|
| 29 |
|
|
|
|
| 40 |
self.model.reset_states()
|
| 41 |
|
| 42 |
|
| 43 |
+
def run(self, audio: np.ndarray, sr: int = 16000):
|
| 44 |
+
audio = np.asarray(audio, dtype=np.float32)
|
| 45 |
+
# record datetime
|
| 46 |
+
cur_ts = datetime.now()
|
| 47 |
+
|
| 48 |
+
# freq scale
|
| 49 |
+
freq_scale = int(sr / self.sr)
|
| 50 |
+
|
| 51 |
+
# inference
|
| 52 |
+
speech_probs = self.model.audio_forward(audio, sr)[0]
|
| 53 |
+
|
| 54 |
+
for i, prob in enumerate(speech_probs):
|
| 55 |
+
audio_slice = audio[i * self.num_samples * freq_scale : (i + 1) * self.num_samples * freq_scale]
|
| 56 |
+
if len(audio_slice) < self.num_samples * freq_scale:
|
| 57 |
+
audio_slice = np.pad(audio_slice, (0, self.num_samples * freq_scale - len(audio_slice)))
|
| 58 |
+
ts = cur_ts.strftime(self.datetime_format)
|
| 59 |
|
| 60 |
# is speech
|
| 61 |
if prob > 1 - self.sensitivity:
|
|
|
|
| 72 |
self.silence_count += 1
|
| 73 |
|
| 74 |
# exceed silence limit
|
| 75 |
+
if 1000 * self.silence_count * self.num_samples / self.sr > self.silence_ms:
|
| 76 |
# return audio segment
|
| 77 |
self.return_data['end_ts'] = ts
|
| 78 |
self.return_data['audio'] = np.concatenate(self.vad_data_list, axis=-1)
|
|
|
|
| 84 |
self.vad_data_list.append(audio_slice)
|
| 85 |
|
| 86 |
# timestamp
|
| 87 |
+
cur_ts += timedelta(seconds=self.num_samples / self.sr)
|
main.py
CHANGED
|
@@ -8,7 +8,7 @@ import soundfile as sf
|
|
| 8 |
def get_args():
|
| 9 |
parser = argparse.ArgumentParser()
|
| 10 |
parser.add_argument('--input', type=str, required=True, help='Input audio file')
|
| 11 |
-
parser.add_argument('--backend', type=str, default='ax650', choices=['ax650', 'ax630c'
|
| 12 |
parser.add_argument('--output_dir', type=str, default='output', help='Output audio dir')
|
| 13 |
return parser.parse_args()
|
| 14 |
|
|
@@ -21,15 +21,15 @@ def main():
|
|
| 21 |
sensitivity=0.5,
|
| 22 |
silence_ms=200)
|
| 23 |
|
| 24 |
-
audio, _ = librosa.load(args.input, sr=vad.
|
| 25 |
i = 0
|
| 26 |
-
for result in vad.run(audio, vad.
|
| 27 |
if result:
|
| 28 |
print(result)
|
| 29 |
filename = os.path.join(args.output_dir, f"{i}.wav")
|
| 30 |
-
sf.write(filename, result['audio'], samplerate=vad.
|
| 31 |
i += 1
|
| 32 |
|
| 33 |
|
| 34 |
if __name__ == '__main__':
|
| 35 |
-
main()
|
|
|
|
| 8 |
def get_args():
|
| 9 |
parser = argparse.ArgumentParser()
|
| 10 |
parser.add_argument('--input', type=str, required=True, help='Input audio file')
|
| 11 |
+
parser.add_argument('--backend', type=str, default='ax650', choices=['ax650', 'ax630c'])
|
| 12 |
parser.add_argument('--output_dir', type=str, default='output', help='Output audio dir')
|
| 13 |
return parser.parse_args()
|
| 14 |
|
|
|
|
| 21 |
sensitivity=0.5,
|
| 22 |
silence_ms=200)
|
| 23 |
|
| 24 |
+
audio, _ = librosa.load(args.input, sr=vad.sr, mono=True)
|
| 25 |
i = 0
|
| 26 |
+
for result in vad.run(audio, vad.sr):
|
| 27 |
if result:
|
| 28 |
print(result)
|
| 29 |
filename = os.path.join(args.output_dir, f"{i}.wav")
|
| 30 |
+
sf.write(filename, result['audio'], samplerate=vad.sr)
|
| 31 |
i += 1
|
| 32 |
|
| 33 |
|
| 34 |
if __name__ == '__main__':
|
| 35 |
+
main()
|
requirements.txt
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
silero_vad_axera
|
|
|
|
| 1 |
+
librosa==0.9.1
|
| 2 |
+
numpy<2.0
|
| 3 |
+
soundfile
|
| 4 |
+
axengine @ https://github.com/AXERA-TECH/pyaxengine/releases/download/0.1.3.rc2/axengine-0.1.3-py3-none-any.whl
|
| 5 |
+
silero-vad-axera>=0.1.2
|
|
|