1.背景:我司AI项目聊天对话框实现语音录入
![]()
2.实现方案: 通过浏览器api实现对语音录入(浏览器默认格式一般为audio/webm)-> 将语音文件上传至cos或取文件链接 -> 将文件链接转给后端(去调用腾讯云的语音识别API)-> 返回给前端识别后的文字
3.遇到的坑:1.腾讯云语音识别文件限制(一般只支持wav,pcm,mp3),即使在生成音频文件时指定文件格式{ type: 'audio/wav' },但是给到腾讯云还是会提示文件格式不支持,文件本质还是webm, 2.文件格式转换 ffmpeg 使用问题
4.实现代码
pnpm代码解读复制代码"@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2",
useCosUploader的实现 可以看上一篇文件上传的二次封装
useTencentASR.ts代码解读复制代码import { getAsrString } from '@/apis/user' import { useCosUploader } from '@/utils/cos' import { convertAudioToWav } from '@/utils/ffmpeg' interface UseRecorderOptions { onEnd?: (text: string) => void } export function useRecorder(options: UseRecorderOptions = {}) { const { onEnd } = options let recorder: MediaRecorder | null = null let chunks: Blob[] = [] const isRecording = ref(false) const isLoading = ref(false) // 是否正在加载(录音过程中) const recordingText = ref('') // 创建试听链接并添加到页面 // const createPreview = (file: File) => { // const url = URL.createObjectURL(file) // console.log('🚀 ~ createPreview ~ url:', url) // // 清理之前的试听播放器(可选) // const existingAudio = document.getElementById('audio-preview') // // eslint-disable-next-line style/max-statements-per-line // if (existingAudio) { existingAudio.remove() } // const audio = new Audio(url) // audio.controls = true // audio.id = 'audio-preview' // document.body.appendChild(audio) // } const startRecording = async () => { // eslint-disable-next-line style/max-statements-per-line if (isRecording.value) { return } isLoading.value = true try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' }) chunks = [] recorder.ondataavailable = (event) => { if (event.data.size > 0) { chunks.push(event.data) } } recorder.onstop = async () => { try { const audioBlob = new Blob(chunks, { type: 'audio/webm' }) // 停止录音时恢复状态 isRecording.value = false isLoading.value = false const wavBlob = await convertAudioToWav(audioBlob) const wavFile = new File([wavBlob], `recording.wav`, { type: 'audio/wav' }) // createPreview(audioFile) // 上传文件转文字 const url = await useCosUploader({ file: wavFile }) console.log('🚀 ~ recorder.onstop= ~ url:', url) const encodedUrl = encodeURIComponent(url) const text = await getAsrString(encodedUrl) recordingText.value = text console.log('🚀 ~ recorder.onstop= ~ text:', text) if (text && onEnd) { onEnd(text) } } catch (err) { console.log('err', err) ElMessage.error('识别失败') } finally { // 停止录音时恢复状态 isRecording.value = false isLoading.value = false } } recorder.start() isRecording.value = true console.log('🎙️ 录音开始') } catch (err) { console.error('🚫 无法获取麦克风权限', err) } } const stopRecording = () => { if (recorder && recorder.state === 'recording') { recorder.stop() } } const toggleRecording = () => { isRecording.value ? stopRecording() : startRecording() } return { isRecording, recordingText, startRecording, stopRecording, toggleRecording, } }
文件格式转换
ffmpeg.ts代码解读复制代码import { FFmpeg } from '@ffmpeg/ffmpeg' import { fetchFile, toBlobURL } from '@ffmpeg/util' // const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/[email protected]/dist/esm' const baseURL = 'https://unpkg.com/@ffmpeg/[email protected]/dist/esm' /** * 将 WebM/Opus 格式音频 Blob 转换为 WAV 格式 * @param blob 录音生成的音频 Blob(audio/webm) * @returns 转换后的 audio/wav 格式 Blob */ // 初始化 ffmpeg 实例 const ffmpeg = new FFmpeg() export async function convertAudioToWav(blob: Blob): Promise{ // // 加载 ffmpeg 核心代码 if (!ffmpeg.loaded) { await ffmpeg.load({ coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), }) } // 写入 webm 文件 await ffmpeg.writeFile('input.webm', await fetchFile(blob)) // 转换为 16bit PCM、16kHz、单声道 wav await ffmpeg.exec([ '-i', 'input.webm', // 输入文件 '-ar', '16000', // 设置采样率为 16kHz '-ac', '1', // 设置为单声道 '-sample_fmt', 's16', // 设置采样格式为 16bit PCM 'output.wav', // 输出文件 ]) // 读取输出文件内容 const data = await ffmpeg.readFile('output.wav') // 返回新的 Blob,供上传等操作使用 return new Blob([data], { type: 'audio/wav' }) }
vite配置
![]()
vite.config.ts代码解读复制代码optimizeDeps: { exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'], }, server: { proxy: { '/ffmpeg': { target: 'https://unpkg.com/@ffmpeg/[email protected]/dist/esm', changeOrigin: true, rewrite: path => path.replace(/^\/ffmpeg/, ''), }, }, },
评论记录:
回复评论: