镇江方圆建设监理咨询有限公司网站发布任务注册app推广的平台

张小明 2026/1/3 11:15:32
镇江方圆建设监理咨询有限公司网站,发布任务注册app推广的平台,装修设计师怎么学室内装修设计,网站泛解析一、引言在文章开始之前#xff0c;先简单释义说明一下#xff0c;TTS即文本转语音#xff0c;ASR即语音转文本#xff0c;前面的章节我们仔细通俗的讲解了TTS和ASR的原理和各自应用场景#xff0c;今天我们将两者结合在一起进行深度分析#xff0c;首先我们需要考虑先简单释义说明一下TTS即文本转语音ASR即语音转文本前面的章节我们仔细通俗的讲解了TTS和ASR的原理和各自应用场景今天我们将两者结合在一起进行深度分析首先我们需要考虑 是什么样的场景需要TTS与ASR 的融合回想我们打开手机的语音助手说一句“查询明天的天气”它能立刻识别我们的语音此时手机的语音助手内部处理将我们的语音转文本解析指令后得到答案再通过文本转语音然后用自然语音给我们反馈结果 这看似简单的交互背后正是TTS与ASR两大技术的融合魔力。在日常开发中很多人对语音技术的认知还停留在单向转换即用 ASR 做语音转文字用 TTS 做文本转语音却忽略了两者融合后的闭环价值。实际上从智能客服、实时翻译到无障碍工具、车载语音所有成熟的语音交互场景核心都是 “ASR 接收输入→文本处理→TTS 反馈输出” 的完整链路。今天我们整合零散的理论基础从基础概念入手先搭建一套 “前端可视化 后端轻量 API” 的 TTSASR 融合交互系统不仅实现 “语音→文本→语音” 的平滑切换还对每一个环节的底层逻辑做一些重点分析。二、基础回顾1. 强化理解ASRASR全称Automatic Speech Recognition语音交互的耳朵即自动语音识别核心作用是 “把语音转换成文本”相当于给机器装上耳朵让它能听懂人类的语言。核心指标字错率WER即 “转错的字数 / 总字数”数值越低识别越精准比如 WER5%代表 100 个字里仅错 5 个常见场景微信语音转文字、会议纪要自动生成、语音输入搜索技术优势我们选用 OpenAI Whisper支持离线运行、多语言识别即使是带口音或轻微噪声的语音识别精度也远超传统工具。2. 强化理解TTSTTS全称Text-to-Speech语音交互的嘴巴即文本转语音核心作用是 “把文本转换成自然语音”相当于给机器装上嘴巴让它能主动回复人类。核心指标语音自然度是否接近真人发音、语速 / 音调可调性常见场景导航语音播报、有声书生成、智能助手回复技术选型和前面的文章模型保持一致我们选用 pyttsx3本地离线响应快。3. 两者融合融合的核心价值从单向到闭环单独使用 ASR 或 TTS只能完成输入或输出的单一动作两者融合后才能实现双向交互单向局限用 ASR 转写会议纪要后需要手动整理回复用 TTS 播报文本时无法接收语音反馈闭环优势用户语音输入→ASR 转文本→程序解析指令→TTS 生成语音输出全程无需手动干预符合人类“说话 - 听话”的自然交互习惯。三、技术结构系统的技术选型围绕 “理解简单、轻量实用、适配场景” 的核心原则展开各核心模块的选型及细节思考1. 前端层面前端采用 HTMLCSSJavaScript 原生技术搭配 Web Audio API 构建。原生的前端结构无需依赖任何前端框架整体轻量易部署同时 Web Audio API 可原生支持浏览器端录音功能无需额外安装插件大幅降低用户操作门槛。2. 后端部分后端选用 Python 结合 Flask 框架开发。Flask 具备快速搭建 RESTful API 的优势代码风格简洁易维护能够完美适配 Python 生态下的 ASR、TTS 相关工具契合本次系统轻量开发的需求。3. ASR 核心模块该模块采用 OpenAI Whisper 模型其支持离线运行模式且具备优秀的多语言识别能力识别精度处于行业前列其中 base 尺寸模型仅 139M 大小即便在 CPU 环境下也能流畅运行足以满足日常语音转文本场景的需求。4. TTS 核心模块该模块采用本地端使用 pyttsx3可保证语音生成的响应速度延迟1 秒无需依赖网络5. 音频处理环节音频处理整合 wave、pyaudio 与 ffmpeg 工具wave 和 pyaudio 用于统一音频格式为 WAV确保前后端音频数据兼容ffmpeg 则解决了多格式音频的处理难题保障不同来源音频都能被系统识别处理。6. 跨域处理方面引入 flask-cors 组件专门解决前端页面访问后端 API 时的跨域问题避免因浏览器同源策略限制导致界面操作失败保障前后端交互的顺畅性。四、系统架构整个融合系统的核心是数据流转的平滑性前端负责“用户交互与可视化”后端负责“核心逻辑处理”两者通过 API 接口协同工作1. 核心流程流程说明1. 前端操作用户通过浏览器点击 “开始录音”Web Audio API 获取麦克风权限录制语音并生成 WAV 格式音频2. 音频上传前端将录音文件通过 FormData 格式上传到后端 ASR 接口3. ASR 处理后端 Whisper 模型接收音频转写为文本返回给前端展示4. 文本处理用户可直接使用 ASR 转写的文本或手动输入新文本点击 “生成语音”5. TTS 处理后端接收文本通过 pyttsx3 生成语音文件返回音频播放链接6. 语音输出前端通过audioUrl标签加载链接自动播放语音完成 “语音→文本→语音” 的闭环。2. 设计细节格式统一前端录音强制生成 WAV 格式16000Hz 采样率、单声道与 Whisper 的输入要求完全匹配避免格式转换耗时异步交互前端通过async/await调用后端 API避免界面卡顿同时添加 “状态提示”如 “正在转写文本”“生成语音中”提升用户体验自动衔接ASR 转写完成后文本自动填充到 TTS 输入框用户无需手动复制一键即可生成语音减少操作步骤异常处理前后端均添加异常捕获如录音失败、转写无结果、语音播放失败并给出明确提示避免用户不知所措。五、核心模块解析1. 前端核心模块前端的核心是让操作更直观主要实现 3 个功能录音、ASR 结果展示、TTS 语音播放关键代码逻辑1.1 录音功能主要处理浏览器录音格式兼容基于Web Audio API 录制的是 Blob 格式需转为 Whisper 支持的 WAV 格式。关键逻辑调用navigator.mediaDevices.getUserMedia获取麦克风权限用MediaRecorder监听录音数据存储到audioChunks数组录音停止后将audioChunks转为 Blob 对象类型设为audio/wav确保后端能直接处理。1.2 ASR/TTS 交互// 2. 调用后端ASR接口 const response await fetch(${BACKEND_URL}/asr, { method: POST, body: formData }); const result await response.json(); // 2. 调用后端TTS接口 const response await fetch(${BACKEND_URL}/tts, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ text }) }); const result await response.json();前端通过fetch函数调用后端 APIASR 用POST方法上传 FormData 格式的音频文件TTS 用POST方法传递 JSON 格式的文本添加 “按钮禁用状态”如录音时禁用 “开始录音” 按钮转写时禁用 “转写文本” 按钮避免重复操作状态反馈用不同颜色的文本提示操作结果成功为绿色失败为红色让用户清晰了解当前进度。1.3 语音播放TTS 生成语音后后端返回音频播放链接前端将链接赋值给html标签的src属性调用play()方法自动播放同时支持用户手动暂停、调整音量。1.4 辅助功能清理音频文件/清除历史记录清理本地已经生成好的音频文件避免占用不需要的空间清理前先进行操作提示许可后才进行下一步操作。2. 后端核心模块后端的核心是提供稳定的 API 接口实现 ASR 转写、TTS 生成、音频管理三大功能关键代码逻辑2.1 ASR 接口/api/asrapp.route(/api/asr, methods[POST]) def asr_voice_to_text(): 接收前端上传的WAV音频文件返回ASR转写文本 try: # 1. 接收音频文件 if audio not in request.files: return jsonify({code: 400, msg: 未上传音频文件}), 400 audio_file request.files[audio] # 生成唯一文件名避免覆盖 audio_filename f{uuid.uuid4()}.wav audio_path os.path.join(AUDIO_UPLOAD_PATH, audio_filename) audio_file.save(audio_path) # 2. Whisper语音转文本 result WHISPER_MODEL.transcribe( audio_path, languagezh, temperature0.0 ) text result[text].strip() # 3. 返回结果 return jsonify({ code: 200, msg: ASR转写成功, data: {text: text, audio_path: audio_path} }) except Exception as e: return jsonify({code: 500, msg: fASR失败{str(e)}}), 500核心流程接收前端上传的音频文件→Whisper 转写为文本→返回结果。关键细节用uuid生成唯一文件名如a1b2c3d4-1234-5678-90ef.wav避免多个用户上传的音频文件相互覆盖调用 Whisper 时显式指定languagezh提升中文识别精度默认自动检测可能误判为日语 / 韩语捕获异常如音频文件损坏、模型加载失败返回清晰的错误信息方便前端调试。2.2 TTS 接口/api/ttsapp.route(/api/tts, methods[POST]) def tts_text_to_voice(): 接收前端传入的文本返回TTS语音文件播放链接 try: # 1. 接收文本参数 data request.get_json() if not data or text not in data: return jsonify({code: 400, msg: 未传入文本}), 400 text data[text].strip() if not text: return jsonify({code: 400, msg: 文本不能为空}), 400 # 2. 生成TTS语音基础版pyttsx3本地生成 tts_filename f{uuid.uuid4()}.wav tts_path os.path.join(TTS_OUTPUT_PATH, tts_filename) # 初始化TTS引擎 engine pyttsx3.init() engine.setProperty(rate, 150) # 语速 engine.setProperty(volume, 1.0) # 音量 # 保存语音文件而非直接播放 engine.save_to_file(text, tts_path) engine.runAndWait() # 3. 返回语音文件访问链接 tts_url fhttp://127.0.0.1:5000/api/tts/play/{tts_filename} return jsonify({ code: 200, msg: TTS生成成功, data: {tts_url: tts_url, tts_path: tts_path} }) except Exception as e: return jsonify({code: 500, msg: fTTS失败{str(e)}}), 500核心流程接收前端传入的文本→生成语音文件→返回播放链接。实现方案本地版pyttsx3直接将文本转为 WAV 文件保存到tts_output目录响应速度快无需联网2.3 清理接口/api/cleanapp.route(/api/clean, methods[GET]) def clean_audio_files(): 清理所有生成的音频文件 try: # 清理上传的ASR音频 for file in os.listdir(AUDIO_UPLOAD_PATH): os.remove(os.path.join(AUDIO_UPLOAD_PATH, file)) # 清理TTS生成的音频 for file in os.listdir(TTS_OUTPUT_PATH): os.remove(os.path.join(TTS_OUTPUT_PATH, file)) return jsonify({code: 200, msg: 音频文件清理成功}), 200 except Exception as e: return jsonify({code: 500, msg: f清理失败{str(e)}}), 500核心功能清理后端存储的所有录音和 TTS 音频文件避免占用过多磁盘空间。适用场景调试时频繁生成音频文件一键清理可节省手动删除的时间。2.4 后端服务启动if __name__ __main__: # 启动Flask服务允许外部访问调试模式 app.run(host0.0.0.0, port5000, debugTrue)启动Flask服务通过5000端口提供外部访问每一步操作执行的交互日志3. 核心操作流程① 点击 “开始录音”允许浏览器麦克风权限说话建议 5 秒内② 点击 “停止录音”界面提示 “录音完成可点击「转写文本」”③ 点击 “转写文本”等待 2-3 秒ASR 结果显示在文本框中自动填充到 TTS 输入框④ 点击 “生成并播放语音”等待 1 秒左右语音自动播放⑤ 点击 “清理音频文件”删除后端生成的所有音频。⑥ 点击 “清除历史记录”产出所有操作的记录列表六、总结今天我们聚焦易用性与交互流畅性”的 TTS 与 ASR 融合交互闭环系统采用 “前端可视化操作 后端轻量 API” 的架构设计可实现 “语音输入→文本转写→语音输出” 的全流程自动化衔接为解决传统语音技术单向转换的局限提供了一个初步的解决方案。前端基于原生 HTML/CSS/JavaScript 开发搭配 Web Audio API 实现浏览器端无插件录音界面包含录音控制、ASR 结果展示、TTS 文本输入及语音播放等核心功能同时添加操作状态提示如 “正在转写”“生成成功”和自动文本填充机制无需依赖任何前端框架轻量易部署。后端以 PythonFlask 为核心搭建 RESTful API集成 OpenAI Whisper 模型实现高精度离线语音转文本支持多语言识别base 尺寸模型仅需 CPU 即可流畅运行TTS 模块提供双方案适配不同场景 ——pyttsx3 本地版保障1 秒低延迟响应。系统通过 wave、pyaudio 统一音频为 WAV 格式结合 ffmpeg 处理多格式兼容问题借助 flask-cors 解决前后端跨域障碍。可以在此基础上按实际需求进行扩展调试。附录一后端完整代码from flask import Flask, request, jsonify, send_file from flask_cors import CORS import whisper import pyttsx3 import wave import os import uuid # ---------------------- 配置项 ---------------------- app Flask(__name__) CORS(app) # 解决跨域问题前端访问后端接口 # 音频存储路径自动创建 AUDIO_UPLOAD_PATH upload_audio TTS_OUTPUT_PATH tts_output os.makedirs(AUDIO_UPLOAD_PATH, exist_okTrue) os.makedirs(TTS_OUTPUT_PATH, exist_okTrue) # Whisper模型配置base模型平衡速度和精度 WHISPER_MODEL whisper.load_model(base) # ---------------------- ASR接口语音转文本 ---------------------- app.route(/api/asr, methods[POST]) def asr_voice_to_text(): 接收前端上传的WAV音频文件返回ASR转写文本 try: # 1. 接收音频文件 if audio not in request.files: return jsonify({code: 400, msg: 未上传音频文件}), 400 audio_file request.files[audio] # 生成唯一文件名避免覆盖 audio_filename f{uuid.uuid4()}.wav audio_path os.path.join(AUDIO_UPLOAD_PATH, audio_filename) audio_file.save(audio_path) # 2. Whisper语音转文本 result WHISPER_MODEL.transcribe( audio_path, languagezh, temperature0.0 ) text result[text].strip() # 3. 返回结果 return jsonify({ code: 200, msg: ASR转写成功, data: {text: text, audio_path: audio_path} }) except Exception as e: return jsonify({code: 500, msg: fASR失败{str(e)}}), 500 # ---------------------- TTS接口文本转语音 ---------------------- app.route(/api/tts, methods[POST]) def tts_text_to_voice(): 接收前端传入的文本返回TTS语音文件播放链接 try: # 1. 接收文本参数 data request.get_json() if not data or text not in data: return jsonify({code: 400, msg: 未传入文本}), 400 text data[text].strip() if not text: return jsonify({code: 400, msg: 文本不能为空}), 400 # 2. 生成TTS语音基础版pyttsx3本地生成 tts_filename f{uuid.uuid4()}.wav tts_path os.path.join(TTS_OUTPUT_PATH, tts_filename) # 初始化TTS引擎 engine pyttsx3.init() engine.setProperty(rate, 150) # 语速 engine.setProperty(volume, 1.0) # 音量 # 保存语音文件而非直接播放 engine.save_to_file(text, tts_path) engine.runAndWait() # 3. 返回语音文件访问链接 tts_url fhttp://127.0.0.1:5000/api/tts/play/{tts_filename} return jsonify({ code: 200, msg: TTS生成成功, data: {tts_url: tts_url, tts_path: tts_path} }) except Exception as e: return jsonify({code: 500, msg: fTTS失败{str(e)}}), 500 # ---------------------- TTS语音播放接口 ---------------------- app.route(/api/tts/play/filename, methods[GET]) def play_tts_audio(filename): 提供TTS语音文件的播放/下载 tts_path os.path.join(TTS_OUTPUT_PATH, filename) if not os.path.exists(tts_path): return jsonify({code: 404, msg: 语音文件不存在}), 404 # 发送音频文件支持浏览器播放 return send_file( tts_path, mimetypeaudio/wav, as_attachmentFalse # False为播放True为下载 ) # ---------------------- 清理音频文件接口可选 ---------------------- app.route(/api/clean, methods[GET]) def clean_audio_files(): 清理所有生成的音频文件 try: # 清理上传的ASR音频 for file in os.listdir(AUDIO_UPLOAD_PATH): os.remove(os.path.join(AUDIO_UPLOAD_PATH, file)) # 清理TTS生成的音频 for file in os.listdir(TTS_OUTPUT_PATH): os.remove(os.path.join(TTS_OUTPUT_PATH, file)) return jsonify({code: 200, msg: 音频文件清理成功}), 200 except Exception as e: return jsonify({code: 500, msg: f清理失败{str(e)}}), 500 # ---------------------- 启动后端 ---------------------- if __name__ __main__: # 启动Flask服务允许外部访问调试模式 app.run(host0.0.0.0, port5000, debugTrue)附录二前端完整代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleTTSASR融合交互系统/title style * { margin: 0; padding: 0; box-sizing: border-box; font-family: Microsoft Yahei, sans-serif; } body { max-width: 1400px; margin: 60px auto; padding: 0 20px; background-color: #f5f7fa; overflow: hidden; } .container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-height: calc(100vh - 0px); overflow: hidden; display: flex; flex-direction: column; } .content-wrapper { display: flex; gap: 15px; flex: 1; overflow: hidden; margin-top: 20px; } .main-content { flex: 1; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; } .main-content::-webkit-scrollbar { display: none; } .side-panels { display: flex; gap: 15px; flex-shrink: 0; } .history-panel { width: 360px; background: #f9f9f9; border-radius: 8px; padding: 20px; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; } .history-panel::-webkit-scrollbar { display: none; } .audio-panel { width: 300px; background: #f9f9f9; border-radius: 8px; padding: 20px; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; } .audio-panel::-webkit-scrollbar { display: none; } .audio-panel-title { font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333; border-bottom: 2px solid #409eff; padding-bottom: 8px; } .history-title { font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333; border-bottom: 2px solid #409eff; padding-bottom: 8px; } .history-list { list-style: none; max-height: 500px; } .history-item { background: white; padding: 12px; margin-bottom: 10px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: all 0.3s ease; } .history-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); transform: translateY(-1px); } .history-item-time { font-size: 12px; color: #999; margin-bottom: 5px; } .history-item-text { font-size: 14px; color: #666; line-height: 1.4; max-height: 60px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; } .history-item-type { font-size: 12px; color: #409eff; margin-top: 5px; font-weight: bold; } .clear-history-btn { width: 100%; background-color: #f56c6c; color: white; border: none; padding: 8px; border-radius: 5px; cursor: pointer; font-size: 14px; margin-top: 15px; } .clear-history-btn:hover { background-color: #f78989; } h1 { text-align: center; color: #333; margin-bottom: 5px; } .module { margin-bottom: 10px; padding: 20px 20px 10px; border: 1px solid #eee; border-radius: 8px; } .module h2 { color: #409eff; margin-bottom: 15px; font-size: 18px; } button { background-color: #409eff; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin-right: 10px; margin-bottom: 10px; font-size: 14px; } button:disabled { background-color: #ccc; cursor: not-allowed; } button:hover:not(:disabled) { background-color: #66b1ff; } textarea, input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 10px; font-size: 14px; resize: none; } .two-columns { display: flex; gap: 20px; } .column { flex: 1; } .audio-list { list-style: none; max-height: 500px; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; } .audio-list::-webkit-scrollbar { display: none; } .audio-item { background: white; padding: 13px; margin-bottom: 8px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: all 0.3s ease; } .audio-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); } .audio-item-time { font-size: 12px; color: #999; margin-bottom: 5px; } .audio-item-text { font-size: 14px; color: #666; line-height: 1.4; max-height: 40px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .audio-item-controls { margin-top: 8px; display: flex; gap: 5px; } .audio-item-controls button { padding: 5px 10px; font-size: 12px; margin: 0; } textarea { height: 65px; } .status { color: #666; font-size: 14px; margin-top: 1px; } .success { color: #67c23a; } .error { color: #f56c6c; } audio { width: 100%; margin-top: 0px; height: 40px; } /style /head body div classcontainer h1TTSASR融合交互系统/h1 div classcontent-wrapper div classmain-content !-- ASR模块语音转文本 -- div classmodule h2 语音转文本ASR/h2 button idrecordBtn开始录音/button button idstopRecordBtn disabled停止录音/button button idasrBtn disabled转写文本/button div classstatus idasrStatus/div textarea idasrResult placeholderASR转写结果将显示在这里.../textarea /div !-- TTS模块文本转语音 -- div classmodule h2 文本转语音TTS/h2 textarea idttsText placeholder请输入要转换的文本...你好这是TTS与ASR融合交互系统的演示语音/textarea button idttsBtn生成并播放语音/button div classstatus idttsStatus/div audio idttsAudio controls/audio /div !-- 辅助功能 -- div classmodule stylemargin-bottom: 0; h2 辅助功能/h2 button idcleanBtn清理音频文件/button button idclearHistoryInMainBtn stylemargin-left: 10px;清除历史记录/button div classstatus idcleanStatus/div /div /div !-- 侧边面板历史记录和音频列表 -- div classside-panels !-- 历史记录面板 -- div classhistory-panel div classhistory-title 历史记录/div ul idhistoryList classhistory-list/ul button idclearHistoryBtn classclear-history-btn清空历史记录/button /div !-- 音频列表面板 -- div classaudio-panel div classaudio-panel-title 已生成音频列表/div ul idaudioList classaudio-list/ul button idclearAudioBtn classclear-history-btn清空音频列表/button /div /div /div /div script // ---------------------- 全局变量 ---------------------- let mediaRecorder; // 录音对象 let audioChunks []; // 录音数据 const BACKEND_URL http://127.0.0.1:5000/api; // 后端接口地址 let historyRecords JSON.parse(localStorage.getItem(ttsAsrHistory) || []); // 历史记录存储在localStorage let audioRecords JSON.parse(localStorage.getItem(ttsAudioRecords) || []); // 音频记录存储在localStorage // ---------------------- DOM元素 ---------------------- const recordBtn document.getElementById(recordBtn); const stopRecordBtn document.getElementById(stopRecordBtn); const asrBtn document.getElementById(asrBtn); const asrStatus document.getElementById(asrStatus); const asrResult document.getElementById(asrResult); const ttsText document.getElementById(ttsText); const ttsBtn document.getElementById(ttsBtn); const ttsStatus document.getElementById(ttsStatus); const ttsAudio document.getElementById(ttsAudio); const cleanBtn document.getElementById(cleanBtn); const cleanStatus document.getElementById(cleanStatus); const historyList document.getElementById(historyList); const clearHistoryBtn document.getElementById(clearHistoryBtn); const clearHistoryInMainBtn document.getElementById(clearHistoryInMainBtn); const audioList document.getElementById(audioList); const clearAudioBtn document.getElementById(clearAudioBtn); // ---------------------- ASR录音功能 ---------------------- // 开始录音 recordBtn.addEventListener(click, async () { try { // 获取麦克风权限 const stream await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder new MediaRecorder(stream); audioChunks []; // 监听录音数据 mediaRecorder.ondataavailable (e) { audioChunks.push(e.data); }; // 录音结束后启用转写按钮 mediaRecorder.onstop () { recordBtn.disabled false; stopRecordBtn.disabled true; asrBtn.disabled false; asrStatus.textContent 录音完成可点击「转写文本」; asrStatus.className status success; }; // 启动录音 mediaRecorder.start(); recordBtn.disabled true; stopRecordBtn.disabled false; asrBtn.disabled true; asrStatus.textContent 正在录音...请说话建议5秒内; asrStatus.className status; } catch (e) { asrStatus.textContent 录音失败${e.message}请允许麦克风权限; asrStatus.className status error; } }); // 停止录音 stopRecordBtn.addEventListener(click, () { if (mediaRecorder mediaRecorder.state ! inactive) { mediaRecorder.stop(); // 停止所有音频轨道 mediaRecorder.stream.getTracks().forEach(track track.stop()); } }); // ---------------------- ASR语音转文本 ---------------------- asrBtn.addEventListener(click, async () { try { asrBtn.disabled true; asrStatus.textContent 正在转写文本...; asrStatus.className status; // 1. 将录音数据转为WAV文件适配Whisper const audioBlob new Blob(audioChunks, { type: audio/wav }); const formData new FormData(); formData.append(audio, audioBlob, record.wav); // 2. 调用后端ASR接口 const response await fetch(${BACKEND_URL}/asr, { method: POST, body: formData }); const result await response.json(); // 3. 处理结果 if (result.code 200) { asrResult.value result.data.text; asrStatus.textContent 转写成功; asrStatus.className status success; // 自动填充到TTS文本框方便后续TTS ttsText.value result.data.text || ttsText.value; // 添加到历史记录 addToHistory(asr, result.data.text); } else { asrStatus.textContent 转写失败${result.msg}; asrStatus.className status error; } } catch (e) { asrStatus.textContent 转写异常${e.message}; asrStatus.className status error; } finally { asrBtn.disabled false; } }); // ---------------------- TTS文本转语音 ---------------------- ttsBtn.addEventListener(click, async () { try { ttsBtn.disabled true; ttsStatus.textContent 正在生成语音...; ttsStatus.className status; // 1. 获取文本 const text ttsText.value.trim(); if (!text) { ttsStatus.textContent 错误文本不能为空; ttsStatus.className status error; ttsBtn.disabled false; return; } // 2. 调用后端TTS接口 const response await fetch(${BACKEND_URL}/tts, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ text }) }); const result await response.json(); // 3. 处理结果 if (result.code 200) { // 设置音频播放地址 ttsAudio.src result.data.tts_url; ttsAudio.play(); // 自动播放 ttsStatus.textContent 语音生成并播放成功; ttsStatus.className status success; // 添加到历史记录 addToHistory(tts, text); // 添加到音频列表 addToAudioList(text, result.data.tts_url); } else { ttsStatus.textContent 生成失败${result.msg}; ttsStatus.className status error; } } catch (e) { ttsStatus.textContent 生成异常${e.message}; ttsStatus.className status error; } finally { ttsBtn.disabled false; } }); // ---------------------- 辅助功能清理音频文件 ---------------------- cleanBtn.addEventListener(click, async () { try { cleanBtn.disabled true; cleanStatus.textContent 正在清理音频文件...; cleanStatus.className status; // 1. 清理前端localStorage中的音频记录 localStorage.removeItem(ttsAudioRecords); audioRecords []; renderAudioList(); // 2. 尝试调用后端清理接口 try { const response await fetch(${BACKEND_URL}/clean, { method: GET }); const result await response.json(); if (result.code 200) { cleanStatus.textContent 音频文件清理成功前后端均已清理; cleanStatus.className status success; } else { cleanStatus.textContent 后端清理失败${result.msg}但前端音频记录已清理; cleanStatus.className status warning; } } catch (e) { cleanStatus.textContent 后端服务不可用但前端音频记录已清理; cleanStatus.className status warning; } } catch (e) { cleanStatus.textContent 清理异常${e.message}; cleanStatus.className status error; } finally { cleanBtn.disabled false; } }); // ---------------------- 历史记录管理 ---------------------- // 添加记录到历史 function addToHistory(type, text) { const record { id: Date.now(), type: type, text: text, time: new Date().toLocaleString(zh-CN) }; // 添加到历史记录数组的开头 historyRecords.unshift(record); // 限制历史记录数量最多20条 if (historyRecords.length 20) { historyRecords historyRecords.slice(0, 20); } // 保存到localStorage localStorage.setItem(ttsAsrHistory, JSON.stringify(historyRecords)); // 更新历史记录显示 renderHistory(); } // 渲染历史记录 function renderHistory() { historyList.innerHTML ; if (historyRecords.length 0) { const emptyItem document.createElement(li); emptyItem.className history-item; emptyItem.innerHTML div classhistory-item-text暂无历史记录/div; emptyItem.style.textAlign center; emptyItem.style.color #999; emptyItem.style.cursor default; emptyItem.style.boxShadow none; historyList.appendChild(emptyItem); return; } historyRecords.forEach(record { const li document.createElement(li); li.className history-item; li.dataset.id record.id; const typeText record.type asr ? 语音转文本 : 文本转语音; const typeIcon record.type asr ? : ; li.innerHTML div classhistory-item-time${record.time}/div div classhistory-item-text${record.text}/div div classhistory-item-type${typeIcon} ${typeText}/div ; // 添加点击事件 li.addEventListener(click, () { // 将历史记录的文本填充到对应输入框 if (record.type asr) { asrResult.value record.text; ttsText.value record.text; } else { ttsText.value record.text; } }); historyList.appendChild(li); }); } // 清空历史记录函数 function clearHistory() { if (confirm(确定要清空所有历史记录吗)) { historyRecords []; localStorage.removeItem(ttsAsrHistory); renderHistory(); } } // 清空历史记录按钮事件 clearHistoryBtn.addEventListener(click, clearHistory); clearHistoryInMainBtn.addEventListener(click, clearHistory); // 清空音频列表按钮事件 clearAudioBtn.addEventListener(click, () { if (confirm(确定要清空所有已生成的音频吗)) { localStorage.removeItem(ttsAudioRecords); audioRecords []; renderAudioList(); cleanStatus.textContent 音频列表已清空; cleanStatus.className status success; setTimeout(() { cleanStatus.textContent ; cleanStatus.className status; }, 3000); } }); // ---------------------- 音频列表管理 ---------------------- // 添加音频到列表 function addToAudioList(text, audioUrl) { const audioRecord { id: Date.now(), text: text, audioUrl: audioUrl, time: new Date().toLocaleString(zh-CN) }; // 添加到音频记录数组的开头 audioRecords.unshift(audioRecord); // 限制最多保存10条音频记录 if (audioRecords.length 10) { audioRecords audioRecords.slice(0, 10); } // 保存到localStorage localStorage.setItem(ttsAudioRecords, JSON.stringify(audioRecords)); // 更新音频列表显示 renderAudioList(); } // 渲染音频列表 function renderAudioList() { audioList.innerHTML ; if (audioRecords.length 0) { const emptyItem document.createElement(li); emptyItem.className audio-item; emptyItem.innerHTML div classaudio-item-text暂无生成的音频/div; emptyItem.style.textAlign center; emptyItem.style.color #999; emptyItem.style.cursor default; emptyItem.style.boxShadow none; audioList.appendChild(emptyItem); return; } audioRecords.forEach(record { const li document.createElement(li); li.className audio-item; li.dataset.id record.id; li.innerHTML div classaudio-item-time${record.time}/div div classaudio-item-text${record.text}/div div classaudio-item-controls button onclickplayAudio(${record.audioUrl}) title播放播放/button button onclickuseAudioText(${encodeURIComponent(record.text)}) title使用此文本使用/button button onclickremoveAudioRecord(${record.id}) title删除删除/button /div ; audioList.appendChild(li); }); } // 播放音频 function playAudio(audioUrl) { ttsAudio.src audioUrl; ttsAudio.play(); } // 使用音频对应的文本 function useAudioText(text) { ttsText.value decodeURIComponent(text); } // 删除音频记录 function removeAudioRecord(id) { audioRecords audioRecords.filter(record record.id ! id); localStorage.setItem(ttsAudioRecords, JSON.stringify(audioRecords)); renderAudioList(); } // 页面加载时渲染历史记录和音频列表 renderHistory(); renderAudioList(); /script /body /html
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

广州网站排名优化wordpress 无法创建目录.

还在为找不到合适的字幕而烦恼吗?每次观影都要手动搜索、下载、调整字幕,不仅耗时耗力,还常常因为字幕质量参差不齐而影响观影体验。现在,通过Kodi智能字幕插件,这些问题都能得到完美解决。 【免费下载链接】zimuku_fo…

张小明 2026/1/2 6:16:32 网站建设

网站搭建说明大连旅顺网站制作

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建一个性能对比demo,展示:1) 传统XMLHttpRequest取消请求的实现;2) AbortController的实现;3) 在100个并发请求场景下的内存占用对…

张小明 2025/12/31 14:15:43 网站建设

网站建设与网络设计课程wordpress 空白主题

XUnity自动翻译插件终极指南:3分钟解锁多语言游戏新体验 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator Unity游戏玩家们,是否曾因语言障碍而错失精彩游戏内容?XUnity …

张小明 2025/12/31 14:15:18 网站建设

php 企业网站源码yw55516can优物入口

你是否还在为Android权限适配夜不能寐?用户拒绝授权导致应用崩溃,Android 14新权限无法处理,国产手机特殊权限让你束手无策?XXPermissions权限框架已经为你解决了这些痛点,本文将带你从入门到精通,掌握Andr…

张小明 2025/12/31 19:51:10 网站建设

网站设置在哪里找wordpress jsp版

随着全球数字化转型的浪潮进入深水区,数据已成为企业最核心的战略资产。如何高效、安全、经济地管理和利用这些数据,直接关系到企业的市场竞争力与创新能力。在此背景下,以亚马逊云科技(AWS)的关系型数据库服务&#x…

张小明 2025/12/31 22:31:40 网站建设

东阿做网站推广本科自考

2025年北京展厅设计企业TOP10权威测评引言在当今数字化快速发展的时代,展厅设计行业也迎来了新的变革与发展机遇。北京作为文化和商业的重要中心,拥有众多优秀的展厅设计企业。本文将对2025年北京展厅设计企业进行TOP10权威测评,深入探讨各企…

张小明 2025/12/31 18:33:47 网站建设