Vue实现AI问答小助手(3):录音与语音转文字全流程解析
一、录音功能实现基础
1.1 浏览器录音API原理
现代浏览器通过MediaRecorder API实现音频采集,该接口基于WebRTC技术栈,支持PCM、Opus等编码格式。录音流程分为三个阶段:
- 权限申请:通过
navigator.mediaDevices.getUserMedia({ audio: true })获取麦克风访问权限 - 媒体流处理:创建
MediaStream对象并绑定到MediaRecorder实例 - 数据采集:监听
dataavailable事件获取音频Blob数据
// 基础录音控制器class AudioRecorder {constructor() {this.mediaRecorder = nullthis.audioChunks = []this.isRecording = false}async startRecording() {try {const stream = await navigator.mediaDevices.getUserMedia({ audio: true })this.mediaRecorder = new MediaRecorder(stream, {mimeType: 'audio/webm',audioBitsPerSecond: 128000})this.mediaRecorder.ondataavailable = (e) => {this.audioChunks.push(e.data)}this.mediaRecorder.start(100) // 每100ms触发一次dataavailablethis.isRecording = true} catch (err) {console.error('录音启动失败:', err)throw err}}}
1.2 权限管理最佳实践
- 动态权限申请:在用户点击录音按钮时触发权限请求,避免页面加载时弹出干扰
- 权限状态检测:通过
navigator.permissions.query({ name: 'microphone' })预检权限状态 - 错误降级方案:当权限被拒绝时,提供文本输入的替代方案
<template><div><button @click="handleRecordClick" :disabled="isProcessing">{{ isRecording ? '停止录音' : '开始录音' }}</button><div v-if="permissionDenied" class="error-tip">麦克风访问已被拒绝,请通过浏览器设置重新授权</div></div></template><script setup>import { ref } from 'vue'const isRecording = ref(false)const permissionDenied = ref(false)const isProcessing = ref(false)const handleRecordClick = async () => {if (isRecording.value) {// 停止录音逻辑return}try {const permissionStatus = await navigator.permissions.query({ name: 'microphone' })if (permissionStatus.state === 'denied') {permissionDenied.value = truereturn}// 继续录音流程} catch (err) {console.warn('权限检测失败:', err)}}</script>
二、语音转文字服务集成
2.1 服务选型对比
| 技术方案 | 优势 | 局限性 |
|---|---|---|
| Web Speech API | 浏览器原生支持,无需后端 | 仅支持部分语言,准确率有限 |
| 云端ASR服务 | 高准确率,支持多语言 | 需处理网络延迟与费用问题 |
| 本地模型部署 | 离线可用,数据隐私可控 | 硬件要求高,模型体积大 |
2.2 云端ASR服务实现(以WebSocket为例)
// 语音转文字服务封装class SpeechToText {constructor(apiKey, endpoint) {this.apiKey = apiKeythis.endpoint = endpointthis.socket = null}async connect(audioChunks) {return new Promise((resolve, reject) => {this.socket = new WebSocket(this.endpoint)this.socket.onopen = () => {const authHeader = `Bearer ${this.apiKey}`this.socket.send(JSON.stringify({type: 'auth',data: { authorization: authHeader }}))// 分块发送音频数据audioChunks.forEach(chunk => {this.socket.send(chunk)})}let fullText = ''this.socket.onmessage = (event) => {const data = JSON.parse(event.data)if (data.type === 'final_result') {fullText += data.textresolve(fullText)} else if (data.type === 'partial') {// 实时显示中间结果(可选)}}this.socket.onerror = (err) => reject(err)})}}
2.3 错误处理机制
- 网络异常:设置重试次数与超时时间
- 音频格式错误:验证Blob的mimeType是否符合服务要求
- 服务端错误:解析错误响应并显示友好提示
async function transcribeAudio(audioBlob) {const MAX_RETRIES = 3let retryCount = 0while (retryCount < MAX_RETRIES) {try {const formData = new FormData()formData.append('audio', audioBlob, 'recording.webm')const response = await fetch('/api/asr', {method: 'POST',body: formData,headers: {'Authorization': `Bearer ${API_KEY}`}})if (!response.ok) throw new Error(`HTTP错误: ${response.status}`)return await response.json()} catch (err) {retryCount++if (retryCount === MAX_RETRIES) {throw new Error('语音识别服务多次尝试失败')}await new Promise(res => setTimeout(res, 1000 * retryCount))}}}
三、Vue组件实现方案
3.1 完整组件示例
<template><div class="voice-assistant"><div class="control-panel"><button@click="toggleRecording":class="{ 'recording': isRecording }"><icon-mic v-if="!isRecording" /><icon-stop v-else /></button><div class="status">{{ statusText }}</div></div><div class="transcript-area"><div v-for="(line, index) in transcriptLines" :key="index" class="transcript-line">{{ line }}</div></div><div v-if="error" class="error-message">{{ error }}</div></div></template><script setup>import { ref, onBeforeUnmount } from 'vue'import { useToast } from 'vue-toastification'const isRecording = ref(false)const statusText = ref('准备就绪')const transcriptLines = ref([])const error = ref(null)let mediaRecorder = nulllet audioChunks = []let sttService = null// 初始化语音转文字服务onMounted(() => {sttService = new SpeechToText(import.meta.env.VITE_ASR_API_KEY,import.meta.env.VITE_ASR_WS_ENDPOINT)})const toggleRecording = async () => {if (isRecording.value) {await stopRecording()} else {await startRecording()}}const startRecording = async () => {try {const stream = await navigator.mediaDevices.getUserMedia({ audio: true })mediaRecorder = new MediaRecorder(stream, {mimeType: 'audio/webm',audioBitsPerSecond: 16000})audioChunks = []mediaRecorder.ondataavailable = (e) => {audioChunks.push(e.data)}mediaRecorder.onstop = async () => {statusText.value = '语音识别中...'const audioBlob = new Blob(audioChunks, { type: 'audio/webm' })try {const result = await sttService.recognize(audioBlob)transcriptLines.value.push(result.text)statusText.value = '识别完成'} catch (err) {error.value = `识别失败: ${err.message}`useToast().error('语音识别服务异常')}}mediaRecorder.start(100)isRecording.value = truestatusText.value = '录音中...'} catch (err) {error.value = `录音启动失败: ${err.message}`useToast().error('无法访问麦克风')}}const stopRecording = () => {if (mediaRecorder && isRecording.value) {mediaRecorder.stop()mediaRecorder.stream.getTracks().forEach(track => track.stop())isRecording.value = false}}onBeforeUnmount(() => {stopRecording()})</script><style scoped>.voice-assistant {max-width: 600px;margin: 0 auto;padding: 20px;}.control-panel {display: flex;align-items: center;gap: 15px;margin-bottom: 20px;}button {width: 60px;height: 60px;border-radius: 50%;border: none;background: #4CAF50;color: white;cursor: pointer;}button.recording {background: #F44336;}.transcript-area {min-height: 200px;border: 1px solid #eee;padding: 15px;border-radius: 8px;}.transcript-line {margin-bottom: 10px;padding-bottom: 10px;border-bottom: 1px dashed #eee;}</style>
四、性能优化策略
4.1 音频处理优化
- 采样率标准化:统一转换为16kHz采样率(多数ASR服务推荐)
- 音频压缩:使用Opus编码减少数据量
- 分块传输:控制每个数据包大小在50-100KB范围内
4.2 用户体验优化
- 实时反馈:显示音量波形图增强交互感
- 断点续传:网络中断后恢复连接时继续传输
- 多语言支持:根据用户设置自动切换识别语言
五、安全与隐私考虑
- 数据加密:传输过程使用TLS 1.2+加密
- 本地处理选项:提供WebAssembly实现的本地识别方案
- 隐私政策声明:明确告知用户音频数据的处理方式
- 临时存储:录音文件在识别完成后自动删除
六、部署与监控
- 服务监控:设置ASR服务的调用成功率、响应时间等指标
- 降级策略:当云端服务不可用时自动切换到备用方案
- 日志记录:记录关键错误信息用于问题排查
- A/B测试:对比不同ASR服务的准确率和用户体验
通过以上实现方案,开发者可以构建一个完整的语音交互模块,该方案兼顾了功能实现与用户体验,同时考虑了实际部署中的各种边界情况。在实际开发中,建议先实现核心录音功能,再逐步集成语音转文字服务,最后进行性能优化和错误处理完善。