简介:本文详细解析了uniapp中如何实现H5录音、上传、实时语音识别及波形可视化功能,覆盖H5、App和小程序三端,提供完整代码示例与兼容性解决方案。
H5端录音主要依赖Web Audio API和MediaRecorder API,核心步骤包括:
navigator.mediaDevices.getUserMedia({ audio: true })const audioContext = new (window.AudioContext || window.webkitAudioContext)()const mediaRecorder = new MediaRecorder(stream)dataavailable事件获取Blob数据
// H5录音完整示例async function startH5Recording() {try {const stream = await navigator.mediaDevices.getUserMedia({ audio: true });const mediaRecorder = new MediaRecorder(stream);const audioChunks = [];mediaRecorder.ondataavailable = event => {audioChunks.push(event.data);};mediaRecorder.onstop = async () => {const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });// 上传逻辑await uploadAudio(audioBlob);};mediaRecorder.start();return { stop: () => mediaRecorder.stop() };} catch (err) {console.error('录音失败:', err);}}
App端需使用uniapp插件市场或原生插件:
plus.audio.getRecorder获取录音对象AVAudioRecorder原生模块luch-request(网络请求)+ cordova-plugin-media(录音)
// App端录音示例(需安装原生插件)function startAppRecording() {const recorder = plus.audio.getRecorder();recorder.record({filename: '_doc/audio/',format: 'wav'},success => {setTimeout(() => {recorder.stop();// 获取文件路径上传const filePath = success.filename;uploadAppAudio(filePath);}, 5000); // 录制5秒},error => console.error('录音错误:', error));}
小程序需使用wx.getRecorderManager API:
"requiredPrivateInfos": ["record"]format: 'mp3', sampleRate: 16000
// 小程序录音示例function startMiniProgramRecording() {const recorderManager = wx.getRecorderManager();recorderManager.onStart(() => console.log('录音开始'));recorderManager.onStop(res => {const tempFilePath = res.tempFilePath;// 上传临时文件uploadMiniProgramAudio(tempFilePath);});recorderManager.start({format: 'mp3',sampleRate: 16000,numberOfChannels: 1});}
| 平台 | 上传方式 | 注意事项 |
|---|---|---|
| H5 | FormData + XMLHttpRequest | 需处理跨域问题 |
| App | plus.io.resolveLocalFileSystemURL | 需先获取文件路径 |
| 小程序 | wx.uploadFile | 需配置合法域名 |
async function uploadAudio(file, platform) {const formData = new FormData();formData.append('audio', file);try {const response = await fetch('https://your-api.com/upload', {method: 'POST',body: formData,headers: platform === 'h5' ? {} : {// App/小程序可能需要额外header'X-Platform': platform}});return await response.json();} catch (error) {console.error('上传失败:', error);}}
// 实时录音并传输示例async function startRealTimeASR() {const stream = await navigator.mediaDevices.getUserMedia({ audio: true });const mediaRecorder = new MediaRecorder(stream, {mimeType: 'audio/webm',audioBitsPerSecond: 16000});const socket = new WebSocket('wss://your-asr-api.com');mediaRecorder.ondataavailable = event => {if (socket.readyState === WebSocket.OPEN) {socket.send(event.data);}};mediaRecorder.start(100); // 每100ms发送一次数据socket.onmessage = event => {const result = JSON.parse(event.data);console.log('识别结果:', result.text);};}
plus.socket.WebSocket实现wx.connectSocket
// 小程序WebSocket示例function startMiniProgramASR() {const socketTask = wx.connectSocket({url: 'wss://your-asr-api.com',protocols: ['audio']});socketTask.onOpen(() => {const recorder = wx.getRecorderManager();recorder.onFrameRecorded(res => {socketTask.send({data: res.frameBuffer,success: () => console.log('发送成功')});});recorder.start({ format: 'pcm' });});}
function drawWaveform(audioContext, stream) {const source = audioContext.createMediaStreamSource(stream);const analyser = audioContext.createAnalyser();analyser.fftSize = 2048;source.connect(analyser);const canvas = document.getElementById('waveform');const ctx = canvas.getContext('2d');const bufferLength = analyser.frequencyBinCount;const dataArray = new Uint8Array(bufferLength);function draw() {analyser.getByteFrequencyData(dataArray);ctx.fillStyle = 'rgb(200, 200, 200)';ctx.fillRect(0, 0, canvas.width, canvas.height);const barWidth = (canvas.width / bufferLength) * 2.5;let x = 0;for (let i = 0; i < bufferLength; i++) {const barHeight = dataArray[i] / 2;ctx.fillStyle = `rgb(${barHeight + 100}, 50, 50)`;ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);x += barWidth + 1;}requestAnimationFrame(draw);}draw();}
小程序需使用canvas组件配合RecorderManager的onFrameRecorded回调:
// 小程序波形绘制Page({onReady() {this.ctx = wx.createCanvasContext('waveformCanvas');this.dataPoints = [];},onFrameRecorded(res) {const buffer = new Uint8Array(res.frameBuffer);const avg = buffer.reduce((a, b) => a + b) / buffer.length;this.dataPoints.push(avg);if (this.dataPoints.length > 100) {this.dataPoints.shift();}this.drawWaveform();},drawWaveform() {const ctx = this.ctx;ctx.clearRect(0, 0, 300, 100);this.dataPoints.forEach((val, i) => {const x = i * 3;const y = 50 - val / 2;ctx.beginPath();ctx.arc(x, y, 1, 0, 2 * Math.PI);ctx.fill();});ctx.draw();}});
//#ifdef APP-PLUS
import { startAppRecording } from ‘./app-recorder’;
//#endif
//#ifdef MP-WEIXIN
import { startMiniProgramRecording } from ‘./mini-recorder’;
//#endif
2. **性能优化策略**:- 录音采样率统一为16kHz(ASR标准)- 波形绘制使用离屏Canvas(H5)- 小程序使用Worker处理音频数据3. **错误处理机制**:```javascriptfunction safeRecord(platform) {try {switch(platform) {case 'h5': return startH5Recording();case 'app': return startAppRecording();case 'mini': return startMiniProgramRecording();default: throw new Error('不支持的平台');}} catch (error) {uni.showToast({title: `录音失败: ${error.message}`,icon: 'none'});// 降级处理逻辑}}
H5端麦克风权限问题:
await navigator.permissions.query({ name: 'microphone' })小程序录音时长限制:
wx.startBackgroundAudio后台录音App端音频格式兼容:
.wav或.amr.m4a或.caf实时识别延迟优化:
本文提供的方案已在多个uniapp项目中验证,开发者可根据实际需求调整参数。建议先实现H5基础功能,再通过条件编译逐步扩展App和小程序支持。对于生产环境,建议搭配专业的ASR服务(如阿里云、腾讯云)以获得更稳定的识别效果。