首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

语音转文字录入,ffmpeg 实现音频格式处理

  • 25-04-20 16:42
  • 3642
  • 11108
juejin.cn

1.背景:我司AI项目聊天对话框实现语音录入

image.png

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配置

image.png

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/, ''), }, }, },
注:本文转载自juejin.cn的Joet的文章"https://juejin.cn/post/7494635102174052387"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

104
前端
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top