137 lines
5.4 KiB
HTML
137 lines
5.4 KiB
HTML
|
|
<!--<!– index.html –>-->
|
||
|
|
<!--<!DOCTYPE html>-->
|
||
|
|
<!--<html lang="zh">-->
|
||
|
|
<!--<head>-->
|
||
|
|
<!-- <meta charset="UTF-8"/>-->
|
||
|
|
<!-- <title>SenseVoice + VAD 实时识别</title>-->
|
||
|
|
<!-- <style>-->
|
||
|
|
<!-- body {-->
|
||
|
|
<!-- font-family: Arial, sans-serif;-->
|
||
|
|
<!-- padding: 40px;-->
|
||
|
|
<!-- }-->
|
||
|
|
|
||
|
|
<!-- button {-->
|
||
|
|
<!-- padding: 12px 24px;-->
|
||
|
|
<!-- font-size: 16px;-->
|
||
|
|
<!-- margin: 10px 5px;-->
|
||
|
|
<!-- }-->
|
||
|
|
|
||
|
|
<!-- #result {-->
|
||
|
|
<!-- margin: 20px 0;-->
|
||
|
|
<!-- padding: 15px;-->
|
||
|
|
<!-- background: #f0f0f0;-->
|
||
|
|
<!-- border-radius: 6px;-->
|
||
|
|
<!-- }-->
|
||
|
|
<!-- </style>-->
|
||
|
|
<!--</head>-->
|
||
|
|
<!--<body>-->
|
||
|
|
|
||
|
|
<!--<h1>🎙️ SenseVoiceSmall 实时语音识别</h1>-->
|
||
|
|
<!--<button id="startBtn">开始监听</button>-->
|
||
|
|
<!--<button id="stopBtn" disabled>停止监听</button>-->
|
||
|
|
<!--<div id="status">状态:初始化中...</div>-->
|
||
|
|
<!--<div id="result">识别结果将显示在这里...</div>-->
|
||
|
|
|
||
|
|
<!--<!– ONNX Runtime Web –>-->
|
||
|
|
<!--<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.14.0/dist/ort.js"></script>-->
|
||
|
|
<!--<!– vad-web –>-->
|
||
|
|
<!--<script src="https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.26/dist/bundle.min.js"></script>-->
|
||
|
|
|
||
|
|
<!--<script>-->
|
||
|
|
<!-- let myvad = null;-->
|
||
|
|
<!-- let websocket = null;-->
|
||
|
|
<!-- const statusDiv = document.getElementById('status');-->
|
||
|
|
<!-- const resultDiv = document.getElementById('result');-->
|
||
|
|
<!-- const startBtn = document.getElementById('startBtn');-->
|
||
|
|
<!-- const stopBtn = document.getElementById('stopBtn');-->
|
||
|
|
<!-- let audioBuffer = [];-->
|
||
|
|
<!-- const BUFFER_THRESHOLD = 1024 * 5; // 缓冲数据达到 5KB 后发送-->
|
||
|
|
|
||
|
|
<!-- // 初始化 VAD-->
|
||
|
|
<!-- async function initVAD() {-->
|
||
|
|
<!-- try {-->
|
||
|
|
<!-- myvad = await vad.MicVAD.new({-->
|
||
|
|
<!-- onSpeechStart: () => {-->
|
||
|
|
<!-- statusDiv.innerText = "🟢 检测到语音,正在发送...";-->
|
||
|
|
<!-- },-->
|
||
|
|
<!-- onSpeechEnd: (audio) => {-->
|
||
|
|
<!-- if (websocket && websocket.readyState === WebSocket.OPEN) {-->
|
||
|
|
<!-- const pcm16 = floatTo16BitPCM(audio);-->
|
||
|
|
<!-- websocket.send(pcm16);-->
|
||
|
|
<!-- statusDiv.innerText = `📤 发送语音段 (${audio.length} 个采样点)`;-->
|
||
|
|
<!-- }-->
|
||
|
|
<!-- },-->
|
||
|
|
<!-- // onFrameProcessed: (probs, frame) => {-->
|
||
|
|
<!-- // // 可以调整阈值,比如 > 0.3 就认为可能是语音-->
|
||
|
|
<!-- // const isLikelySpeech = probs.isSpeech > 0.3;-->
|
||
|
|
<!-- // audioBuffer.push(frame); // 缓存每一段音频数据-->
|
||
|
|
<!-- //-->
|
||
|
|
<!-- // if (isLikelySpeech && websocket?.readyState === WebSocket.OPEN) {-->
|
||
|
|
<!-- // const pcm16 = floatTo16BitPCM(frame);-->
|
||
|
|
<!-- // websocket.send(pcm16);-->
|
||
|
|
<!-- // }-->
|
||
|
|
<!-- // }-->
|
||
|
|
<!-- });-->
|
||
|
|
<!-- statusDiv.innerText = "✅ VAD 初始化完成,点击【开始监听】";-->
|
||
|
|
<!-- } catch (err) {-->
|
||
|
|
<!-- statusDiv.innerText = `❌ VAD 加载失败: ${err.message}`;-->
|
||
|
|
<!-- }-->
|
||
|
|
<!-- }-->
|
||
|
|
|
||
|
|
<!-- // 连接 WebSocket-->
|
||
|
|
<!-- function connectWebSocket() {-->
|
||
|
|
<!-- websocket = new WebSocket('ws://localhost:8000/ws/asr');-->
|
||
|
|
<!-- websocket.onopen = () => {-->
|
||
|
|
<!-- statusDiv.innerText = "🟢 WebSocket 已连接";-->
|
||
|
|
<!-- };-->
|
||
|
|
<!-- websocket.onmessage = (e) => {-->
|
||
|
|
<!-- try {-->
|
||
|
|
<!-- const data = JSON.parse(e.data);-->
|
||
|
|
<!-- resultDiv.innerText = data.text || "";-->
|
||
|
|
<!-- } catch {-->
|
||
|
|
<!-- resultDiv.innerText = e.data;-->
|
||
|
|
<!-- }-->
|
||
|
|
<!-- };-->
|
||
|
|
<!-- websocket.onerror = (e) => {-->
|
||
|
|
<!-- statusDiv.innerText = "❌ WebSocket 错误";-->
|
||
|
|
<!-- };-->
|
||
|
|
<!-- websocket.onclose = () => {-->
|
||
|
|
<!-- statusDiv.innerText = "🔌 已断开连接,正在重连...";-->
|
||
|
|
<!-- setTimeout(connectWebSocket, 3000); // 重连-->
|
||
|
|
<!-- };-->
|
||
|
|
<!-- }-->
|
||
|
|
|
||
|
|
<!-- // Float32 → 16-bit PCM-->
|
||
|
|
<!-- function floatTo16BitPCM(float32Array) {-->
|
||
|
|
<!-- const buffer = new ArrayBuffer(float32Array.length * 2);-->
|
||
|
|
<!-- const view = new DataView(buffer);-->
|
||
|
|
<!-- for (let i = 0; i < float32Array.length; i++) {-->
|
||
|
|
<!-- const s = Math.max(-1, Math.min(1, float32Array[i]));-->
|
||
|
|
<!-- const val = s < 0 ? s * 0x8000 : s * 0x7FFF;-->
|
||
|
|
<!-- view.setInt16(i * 2, val, true);-->
|
||
|
|
<!-- }-->
|
||
|
|
<!-- return buffer;-->
|
||
|
|
<!-- }-->
|
||
|
|
|
||
|
|
<!-- // 控制按钮-->
|
||
|
|
<!-- startBtn.addEventListener('click', () => {-->
|
||
|
|
<!-- if (myvad) myvad.start();-->
|
||
|
|
<!-- startBtn.disabled = true;-->
|
||
|
|
<!-- stopBtn.disabled = false;-->
|
||
|
|
<!-- });-->
|
||
|
|
|
||
|
|
<!-- stopBtn.addEventListener('click', () => {-->
|
||
|
|
<!-- if (myvad) myvad.stop();-->
|
||
|
|
<!-- startBtn.disabled = false;-->
|
||
|
|
<!-- stopBtn.disabled = true;-->
|
||
|
|
<!-- statusDiv.innerText = "⏸️ 已停止监听";-->
|
||
|
|
<!-- });-->
|
||
|
|
|
||
|
|
<!-- // 启动-->
|
||
|
|
<!-- window.onload = () => {-->
|
||
|
|
<!-- initVAD();-->
|
||
|
|
<!-- connectWebSocket();-->
|
||
|
|
<!-- };-->
|
||
|
|
<!--</script>-->
|
||
|
|
<!--</body>-->
|
||
|
|
<!--</html>-->
|