stram代码完整代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.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[typefile] { margin: 10px 0; } /style /head body div classcontainer h2未来之窗-SenseVoice-CPP 8 语音client testa/h2 !-- HTTP Stream 实时录音识别AudioWorklet实现 -- div classpanel h3\u1F399\uFE0F 实时麦克风录音HTTP Stream/h3 button classbtn green idstartRecord开始录音/button button classbtn red idstopRecord disabled停止录音/button div classstatus idstreamStatus等待录音/div div classresult-box idrealResult实时识别结果/div /div !-- HTTP 文件上传独立使用 -- div classpanel h3\u1F4E2 音频文件上传HTTP POST/h3 input typefile idaudioFile acceptaudio/* button classbtn iduploadBtn上传并识别/button div classstatus iduploadStatus等待选择文件/div div classresult-box idfileResult文件识别结果/div /div /div script // 配置项 const STREAM_URL http://127.0.0.1:20369/stream; const HTTP_UPLOAD_URL http://127.0.0.1:20369/asr; // // 全局状态 let isRecording false; let audioContext null; let mediaStream null; let workletNode null; let streamReader null; let streamController null; // 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处理器无需单独js文件 const createAudioWorkletModule () { const processorCode class AudioCaptureProcessor extends AudioWorkletProcessor { constructor() { super(); // 监听主线程消息无额外逻辑仅做采集 } process(inputs, outputs, parameters) { const input inputs[0]; if (input.length 0) return true; const channelData input[0]; // 将Float32音频数据发送到主线程 this.port.postMessage(channelData); return true; } } registerProcessor(audio-capture-processor, AudioCaptureProcessor); ; // 转成Blob URL模拟模块加载 const blob new Blob([processorCode], { type: application/javascript }); return URL.createObjectURL(blob); }; // Float32转Int16 PCM适配C服务的extract_pcm const float32ToInt16 (float32Array) { const int16Array new Int16Array(float32Array.length); for (let i 0; i float32Array.length; i) { const val Math.max(-1, Math.min(1, float32Array[i])); int16Array[i] val 0 ? val * 0x8000 : val * 0x7FFF; } return int16Array; }; // 第二步开始录音-核心逻辑AudioWorkletHTTP Stream startRecord.onclick async () { if (isRecording) return; isRecording true; startRecord.disabled true; stopRecord.disabled false; try { // 1. 获取麦克风媒体流16000采样率单声道 mediaStream await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 1, echoCancellation: false, noiseSuppression: false } }); streamStatus.textContent \u23F1\uFE0F 录音中...建立流连接; // 2. 初始化音频上下文强制16000采样率 audioContext new AudioContext({ sampleRate: 16000 }); // 加载内联的AudioWorklet模块 await audioContext.audioWorklet.addModule(createAudioWorkletModule()); // 3. 创建AudioWorkletNode连接麦克风流 workletNode new AudioWorkletNode(audioContext, audio-capture-processor); const source audioContext.createMediaStreamSource(mediaStream); source.connect(workletNode); workletNode.connect(audioContext.destination); // 4. 建立HTTP Stream长连接POST /stream const fetchResponse await fetch(STREAM_URL, { method: POST, headers: { Content-Type: application/octet-stream, Connection: keep-alive, Cache-Control: no-cache }, body: new ReadableStream({ start(controller) { streamController controller; // 监听AudioWorklet的音频数据推送到C服务 workletNode.port.onmessage (e) { if (!isRecording) return; const int16Data float32ToInt16(e.data); streamController.enqueue(int16Data); }; }, cancel() { streamController null; } }) }); // 5. 实时读取C服务的识别结果 if (!fetchResponse.ok) throw new Error(HTTP错误${fetchResponse.status}); streamStatus.textContent \u23F1\uFE0F 录音中...识别中; streamReader fetchResponse.body.getReader(); const decoder new TextDecoder(); // 循环读取结果 const readRecognizeResult async () { while (isRecording) { const { done, value } await streamReader.read(); if (done) break; const result decoder.decode(value, { stream: true }); realResult.textContent 实时识别结果\n result; } }; readRecognizeResult(); } catch (err) { // 精准判断错误类型不再统一提示 isRecording false; startRecord.disabled false; stopRecord.disabled true; if (err.name NotAllowedError) { streamStatus.textContent \u274C 麦克风权限被拒绝请在浏览器设置中开启; } else if (err.message.includes(HTTP)) { streamStatus.textContent \u274C 连接C服务失败${err.message}; } else { streamStatus.textContent \u274C 初始化失败${err.message}; } console.error(录音启动失败, err); } }; // 第三步停止录音-释放所有资源 stopRecord.onclick () { if (!isRecording) return; isRecording false; // 1. 停止麦克风流 if (mediaStream) { mediaStream.getTracks().forEach(track track.stop()); mediaStream null; } // 2. 关闭音频上下文和Worklet if (audioContext) audioContext.close(); if (workletNode) { workletNode.port.close(); workletNode.disconnect(); workletNode null; } // 3. 关闭流读取和推送 if (streamReader) streamReader.cancel(); if (streamController) streamController.close(); // 4. 重置UI和状态 startRecord.disabled false; stopRecord.disabled true; streamStatus.textContent \u2705 录音已停止; }; // 音频文件上传原逻辑保留适配/asr audioFile.onchange () { if (audioFile.files.length 0) { uploadStatus.textContent 已选择 audioFile.files[0].name; } }; uploadBtn.onclick async () { const file audioFile.files[0]; if (!file) { uploadStatus.textContent \u274C 请先选择音频文件; return; } uploadStatus.textContent \u23F3 上传识别中...; fileResult.textContent 文件识别结果处理中...; try { const formData new FormData(); formData.append(audio, file); const res await fetch(HTTP_UPLOAD_URL, { method: POST, body: formData }); const text await res.text(); fileResult.textContent 文件识别结果\n text; uploadStatus.textContent \u2705 识别完成; } catch (e) { uploadStatus.textContent \u274C 上传失败请检查C服务是否启动; fileResult.textContent 文件识别结果上传失败; console.error(文件上传失败, e); } }; /script /body /html人人皆为创造者共创方能共成长每个人都是使用者也是创造者是数字世界的消费者更是价值的生产者与分享者。在智能时代的浪潮里单打独斗的发展模式早已落幕唯有开放连接、创意共创、利益共享才能让个体价值汇聚成生态合力让技术与创意双向奔赴实现平台与伙伴的快速成长、共赢致远。原创永久分成共赴星辰大海原创创意共创、永久收益分成是东方仙盟始终坚守的核心理念。我们坚信每一份原创智慧都值得被尊重与回馈以永久分成锚定共创初心让创意者长期享有价值红利携手万千伙伴向着科技星辰大海笃定前行拥抱硅基 生命与数字智能交融的未来共筑跨越时代的数字文明共同体。东方仙盟拥抱知识开源共筑数字新生态在全球化与数字化浪潮中东方仙盟始终秉持开放协作、知识共享的理念积极拥抱开源技术与开放标准。我们相信唯有打破技术壁垒、汇聚全球智慧才能真正推动行业的可持续发展。开源赋能中小商户通过将前端异常检测、跨系统数据互联等核心能力开源化东方仙盟为全球中小商户提供了低成本、高可靠的技术解决方案让更多商家能够平等享受数字转型的红利。共建行业标准我们积极参与国际技术社区与全球开发者、合作伙伴共同制定开放协议 与技术规范推动跨境零售、文旅、餐饮等多业态的系统互联互通构建更加公平、高效的数字生态。知识普惠共促发展通过开源社区 、技术文档与培训体系东方仙盟致力于将前沿技术转化为可落地的行业实践赋能全球合作伙伴共同培育创新人才推动数字经济 的普惠式增长阿雪技术观在科技发展浪潮中我们不妨积极投身技术共享。不满足于做受益者更要主动担当贡献者。无论是分享代码、撰写技术博客还是参与开源项目 维护改进每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地我们携手在此探索硅基 生命为科技进步添砖加瓦。Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Dont just be the one reaping all the benefits; step up and be a contributor too. Whether youre 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. Were gonna team up and explore the whole silicon - based life thing, and in the process, well be fueling the growth of technology
SenseVoicecpp http steam服务[AI人工智能(七十四)]—东方仙盟
stram代码完整代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.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[typefile] { margin: 10px 0; } /style /head body div classcontainer h2未来之窗-SenseVoice-CPP 8 语音client testa/h2 !-- HTTP Stream 实时录音识别AudioWorklet实现 -- div classpanel h3\u1F399\uFE0F 实时麦克风录音HTTP Stream/h3 button classbtn green idstartRecord开始录音/button button classbtn red idstopRecord disabled停止录音/button div classstatus idstreamStatus等待录音/div div classresult-box idrealResult实时识别结果/div /div !-- HTTP 文件上传独立使用 -- div classpanel h3\u1F4E2 音频文件上传HTTP POST/h3 input typefile idaudioFile acceptaudio/* button classbtn iduploadBtn上传并识别/button div classstatus iduploadStatus等待选择文件/div div classresult-box idfileResult文件识别结果/div /div /div script // 配置项 const STREAM_URL http://127.0.0.1:20369/stream; const HTTP_UPLOAD_URL http://127.0.0.1:20369/asr; // // 全局状态 let isRecording false; let audioContext null; let mediaStream null; let workletNode null; let streamReader null; let streamController null; // 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处理器无需单独js文件 const createAudioWorkletModule () { const processorCode class AudioCaptureProcessor extends AudioWorkletProcessor { constructor() { super(); // 监听主线程消息无额外逻辑仅做采集 } process(inputs, outputs, parameters) { const input inputs[0]; if (input.length 0) return true; const channelData input[0]; // 将Float32音频数据发送到主线程 this.port.postMessage(channelData); return true; } } registerProcessor(audio-capture-processor, AudioCaptureProcessor); ; // 转成Blob URL模拟模块加载 const blob new Blob([processorCode], { type: application/javascript }); return URL.createObjectURL(blob); }; // Float32转Int16 PCM适配C服务的extract_pcm const float32ToInt16 (float32Array) { const int16Array new Int16Array(float32Array.length); for (let i 0; i float32Array.length; i) { const val Math.max(-1, Math.min(1, float32Array[i])); int16Array[i] val 0 ? val * 0x8000 : val * 0x7FFF; } return int16Array; }; // 第二步开始录音-核心逻辑AudioWorkletHTTP Stream startRecord.onclick async () { if (isRecording) return; isRecording true; startRecord.disabled true; stopRecord.disabled false; try { // 1. 获取麦克风媒体流16000采样率单声道 mediaStream await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 1, echoCancellation: false, noiseSuppression: false } }); streamStatus.textContent \u23F1\uFE0F 录音中...建立流连接; // 2. 初始化音频上下文强制16000采样率 audioContext new AudioContext({ sampleRate: 16000 }); // 加载内联的AudioWorklet模块 await audioContext.audioWorklet.addModule(createAudioWorkletModule()); // 3. 创建AudioWorkletNode连接麦克风流 workletNode new AudioWorkletNode(audioContext, audio-capture-processor); const source audioContext.createMediaStreamSource(mediaStream); source.connect(workletNode); workletNode.connect(audioContext.destination); // 4. 建立HTTP Stream长连接POST /stream const fetchResponse await fetch(STREAM_URL, { method: POST, headers: { Content-Type: application/octet-stream, Connection: keep-alive, Cache-Control: no-cache }, body: new ReadableStream({ start(controller) { streamController controller; // 监听AudioWorklet的音频数据推送到C服务 workletNode.port.onmessage (e) { if (!isRecording) return; const int16Data float32ToInt16(e.data); streamController.enqueue(int16Data); }; }, cancel() { streamController null; } }) }); // 5. 实时读取C服务的识别结果 if (!fetchResponse.ok) throw new Error(HTTP错误${fetchResponse.status}); streamStatus.textContent \u23F1\uFE0F 录音中...识别中; streamReader fetchResponse.body.getReader(); const decoder new TextDecoder(); // 循环读取结果 const readRecognizeResult async () { while (isRecording) { const { done, value } await streamReader.read(); if (done) break; const result decoder.decode(value, { stream: true }); realResult.textContent 实时识别结果\n result; } }; readRecognizeResult(); } catch (err) { // 精准判断错误类型不再统一提示 isRecording false; startRecord.disabled false; stopRecord.disabled true; if (err.name NotAllowedError) { streamStatus.textContent \u274C 麦克风权限被拒绝请在浏览器设置中开启; } else if (err.message.includes(HTTP)) { streamStatus.textContent \u274C 连接C服务失败${err.message}; } else { streamStatus.textContent \u274C 初始化失败${err.message}; } console.error(录音启动失败, err); } }; // 第三步停止录音-释放所有资源 stopRecord.onclick () { if (!isRecording) return; isRecording false; // 1. 停止麦克风流 if (mediaStream) { mediaStream.getTracks().forEach(track track.stop()); mediaStream null; } // 2. 关闭音频上下文和Worklet if (audioContext) audioContext.close(); if (workletNode) { workletNode.port.close(); workletNode.disconnect(); workletNode null; } // 3. 关闭流读取和推送 if (streamReader) streamReader.cancel(); if (streamController) streamController.close(); // 4. 重置UI和状态 startRecord.disabled false; stopRecord.disabled true; streamStatus.textContent \u2705 录音已停止; }; // 音频文件上传原逻辑保留适配/asr audioFile.onchange () { if (audioFile.files.length 0) { uploadStatus.textContent 已选择 audioFile.files[0].name; } }; uploadBtn.onclick async () { const file audioFile.files[0]; if (!file) { uploadStatus.textContent \u274C 请先选择音频文件; return; } uploadStatus.textContent \u23F3 上传识别中...; fileResult.textContent 文件识别结果处理中...; try { const formData new FormData(); formData.append(audio, file); const res await fetch(HTTP_UPLOAD_URL, { method: POST, body: formData }); const text await res.text(); fileResult.textContent 文件识别结果\n text; uploadStatus.textContent \u2705 识别完成; } catch (e) { uploadStatus.textContent \u274C 上传失败请检查C服务是否启动; fileResult.textContent 文件识别结果上传失败; console.error(文件上传失败, e); } }; /script /body /html人人皆为创造者共创方能共成长每个人都是使用者也是创造者是数字世界的消费者更是价值的生产者与分享者。在智能时代的浪潮里单打独斗的发展模式早已落幕唯有开放连接、创意共创、利益共享才能让个体价值汇聚成生态合力让技术与创意双向奔赴实现平台与伙伴的快速成长、共赢致远。原创永久分成共赴星辰大海原创创意共创、永久收益分成是东方仙盟始终坚守的核心理念。我们坚信每一份原创智慧都值得被尊重与回馈以永久分成锚定共创初心让创意者长期享有价值红利携手万千伙伴向着科技星辰大海笃定前行拥抱硅基 生命与数字智能交融的未来共筑跨越时代的数字文明共同体。东方仙盟拥抱知识开源共筑数字新生态在全球化与数字化浪潮中东方仙盟始终秉持开放协作、知识共享的理念积极拥抱开源技术与开放标准。我们相信唯有打破技术壁垒、汇聚全球智慧才能真正推动行业的可持续发展。开源赋能中小商户通过将前端异常检测、跨系统数据互联等核心能力开源化东方仙盟为全球中小商户提供了低成本、高可靠的技术解决方案让更多商家能够平等享受数字转型的红利。共建行业标准我们积极参与国际技术社区与全球开发者、合作伙伴共同制定开放协议 与技术规范推动跨境零售、文旅、餐饮等多业态的系统互联互通构建更加公平、高效的数字生态。知识普惠共促发展通过开源社区 、技术文档与培训体系东方仙盟致力于将前沿技术转化为可落地的行业实践赋能全球合作伙伴共同培育创新人才推动数字经济 的普惠式增长阿雪技术观在科技发展浪潮中我们不妨积极投身技术共享。不满足于做受益者更要主动担当贡献者。无论是分享代码、撰写技术博客还是参与开源项目 维护改进每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地我们携手在此探索硅基 生命为科技进步添砖加瓦。Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Dont just be the one reaping all the benefits; step up and be a contributor too. Whether youre 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. Were gonna team up and explore the whole silicon - based life thing, and in the process, well be fueling the growth of technology