SenseVoicecpp http 分片服务[AI人工智能(七十五)]—东方仙盟

核心代码

完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>未来之窗-SenseVoice-CPP 8 语音client testa</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: "Microsoft YaHei", sans-serif;
}
body {
max-width: 800px;
margin: 30px auto;
padding: 0 20px;
background: #f5f7fa;
}
.container {
background: white;
padding: 25px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h2 {
text-align: center;
color: #333;
margin-bottom: 25px;
}
.panel {
margin: 20px 0;
padding: 15px;
border: 1px solid #eee;
border-radius: 8px;
}
.panel h3 {
color: #409eff;
margin-bottom: 12px;
font-size: 16px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
margin-right: 10px;
background: #409eff;
color: white;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.red { background: #f56c6c; }
.green { background: #67c23a; }
.status {
margin: 10px 0;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
background: #f0f2f5;
}
.result-box {
margin-top: 15px;
padding: 15px;
min-height: 80px;
border: 1px dashed #999;
border-radius: 6px;
white-space: pre-wrap;
color: #333;
}
input[type="file"] {
margin: 10px 0;
}
</style>
</head>
<body>
<div class="container">
<h2>未来之窗-SenseVoice-CPP 8 分片技术 语音client </h2>
<div class="panel">
<h3> 实时麦克风录音(HTTP短连接 0.7s/次)</h3>
<button class="btn green" id="startRecord">开始录音</button>
<button class="btn red" id="stopRecord" disabled>停止录音</button>
<div class="status" id="streamStatus">等待录音</div>
<div class="result-box" id="realResult">实时识别结果:</div>
</div>
<div class="panel">
<h3>音频文件上传(HTTP POST)</h3>
<input type="file" id="audioFile" accept="audio/*">
<button class="btn" id="uploadBtn">上传并识别</button>
<div class="status" id="uploadStatus">等待选择文件</div>
<div class="result-box" id="fileResult">文件识别结果:</div>
</div>
</div>
<script>
// ====================== 可配置变量(你要的时长都在这里)======================
const UPLOAD_INTERVAL_SEC = 0.7; // 每0.7秒发送一次
const OVERLAP_SEC = 0.3; // 保留上一段0.3秒
const TOTAL_CHUNK_SEC = 1.0; // 每次发送总时长1秒
const SAMPLE_RATE = 16000; // 采样率
const CHANNEL_COUNT = 1; // 单声道
const SHORT_URL = "http://127.0.0.1:20369/asr"; // 短连接接口
const HTTP_UPLOAD_URL = "http://127.0.0.1:20369/asr";
// =========================================================================
let isRecording = false;
let audioContext = null;
let mediaStream = null;
let workletNode = null;
let sendTimer = null;
// 音频缓存
let audioBuffer = [];
const overlapSampleCount = Math.floor(SAMPLE_RATE * OVERLAP_SEC);
const chunkSampleCount = Math.floor(SAMPLE_RATE * TOTAL_CHUNK_SEC);
// DOM
const startRecord = document.getElementById("startRecord");
const stopRecord = document.getElementById("stopRecord");
const streamStatus = document.getElementById("streamStatus");
const realResult = document.getElementById("realResult");
const audioFile = document.getElementById("audioFile");
const uploadBtn = document.getElementById("uploadBtn");
const uploadStatus = document.getElementById("uploadStatus");
const fileResult = document.getElementById("fileResult");
// 动态创建AudioWorklet
const createAudioWorkletModule = () => {
const processorCode = `
class AudioCaptureProcessor extends AudioWorkletProcessor {
process(inputs) {
const input = inputs[0];
if (input.length > 0) this.port.postMessage(input[0]);
return true;
}
}
registerProcessor('audio-capture-processor', AudioCaptureProcessor);
`;
return URL.createObjectURL(new Blob([processorCode], { type: 'application/javascript' }));
};
// 浮点数转16位PCM
const float32ToInt16 = (float32Array) => {
const int16 = new Int16Array(float32Array.length);
for (let i = 0; i < float32Array.length; i++) {
const v = Math.max(-1, Math.min(1, float32Array[i]));
int16[i] = v < 0 ? v * 0x8000 : v * 0x7FFF;
}
return int16;
};
// 发送音频片段(短连接POST)
const sendAudioChunk = async (pcmData) => {
try {
const res = await fetch(SHORT_URL, {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: pcmData.buffer
});
const text = await res.text();
realResult.textContent = `实时识别结果:\n` + text;
} catch (e) {
console.error("发送失败", e);
}
};
// 开始录音
startRecord.onclick = async () => {
if (isRecording) return;
isRecording = true;
startRecord.disabled = true;
stopRecord.disabled = false;
streamStatus.textContent = " 录音中(0.7s发送一次)";
audioBuffer = [];
try {
mediaStream = await navigator.mediaDevices.getUserMedia({
audio: { sampleRate: SAMPLE_RATE, channelCount: CHANNEL_COUNT }
});
audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
await audioContext.audioWorklet.addModule(createAudioWorkletModule());
workletNode = new AudioWorkletNode(audioContext, 'audio-capture-processor');
const source = audioContext.createMediaStreamSource(mediaStream);
source.connect(workletNode);
// 收集音频
workletNode.port.onmessage = (e) => {
if (!isRecording) return;
audioBuffer.push(...e.data);
};
// 定时器:每0.7秒发一次
sendTimer = setInterval(() => {
if (audioBuffer.length >= chunkSampleCount) {
// 取最后1秒数据
const chunk = audioBuffer.slice(-chunkSampleCount);
const pcm = float32ToInt16(new Float32Array(chunk));
sendAudioChunk(pcm);
// 保留最后0.3秒用于下一次重叠
audioBuffer = audioBuffer.slice(-overlapSampleCount);
}
}, UPLOAD_INTERVAL_SEC * 1000);
} catch (err) {
console.error("录音失败", err);
streamStatus.textContent = "❌ 启动失败:" + err.message;
isRecording = false;
startRecord.disabled = false;
stopRecord.disabled = true;
}
};
// 停止录音
stopRecord.onclick = () => {
if (!isRecording) return;
isRecording = false;
clearInterval(sendTimer);
if (mediaStream) mediaStream.getTracks().forEach(t => t.stop());
if (audioContext) audioContext.close();
if (workletNode) workletNode.port.close();
audioBuffer = [];
startRecord.disabled = false;
stopRecord.disabled = true;
streamStatus.textContent = "✅ 已停止";
};
// 文件上传
audioFile.onchange = () => {
if (audioFile.files.length) {
uploadStatus.textContent = "已选择:" + audioFile.files[0].name;
}
};
uploadBtn.onclick = async () => {
const file = audioFile.files[0];
if (!file) {
uploadStatus.textContent = "❌ 请选择文件";
return;
}
uploadStatus.textContent = "⏳ 上传中...";
const fd = new FormData();
fd.append("audio", file);
try {
const res = await fetch(HTTP_UPLOAD_URL, { method: "POST", body: fd });
const text = await res.text();
fileResult.textContent = "文件识别结果:\n" + text;
uploadStatus.textContent = "✅ 识别完成";
} catch (e) {
uploadStatus.textContent = "❌ 上传失败";
}
};
</script>
</body>
</html>
人人皆为创造者,共创方能共成长
每个人都是使用者,也是创造者;是数字世界的消费者,更是价值的生产者与分享者。在智能时代的浪潮里,单打独斗的发展模式早已落幕,唯有开放连接、创意共创、利益共享,才能让个体价值汇聚成生态合力,让技术与创意双向奔赴,实现平台与伙伴的快速成长、共赢致远。
原创永久分成,共赴星辰大海
原创创意共创、永久收益分成,是东方仙盟始终坚守的核心理念。我们坚信,每一份原创智慧都值得被尊重与回馈,以永久分成锚定共创初心,让创意者长期享有价值红利,携手万千伙伴向着科技星辰大海笃定前行,拥抱硅基 生命与数字智能交融的未来,共筑跨越时代的数字文明共同体。
东方仙盟:拥抱知识开源,共筑数字新生态
在全球化与数字化浪潮中,东方仙盟始终秉持开放协作、知识共享的理念,积极拥抱开源技术与开放标准。我们相信,唯有打破技术壁垒、汇聚全球智慧,才能真正推动行业的可持续发展。
开源赋能中小商户:通过将前端异常检测、跨系统数据互联等核心能力开源化,东方仙盟为全球中小商户提供了低成本、高可靠的技术解决方案,让更多商家能够平等享受数字转型的红利。
共建行业标准:我们积极参与国际技术社区,与全球开发者、合作伙伴共同制定开放协议 与技术规范,推动跨境零售、文旅、餐饮等多业态的系统互联互通,构建更加公平、高效的数字生态。
知识普惠,共促发展:通过开源社区 、技术文档与培训体系,东方仙盟致力于将前沿技术转化为可落地的行业实践,赋能全球合作伙伴,共同培育创新人才,推动数字经济 的普惠式增长
阿雪技术观
在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目 维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基 生命,为科技进步添砖加瓦。
Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets , hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)