最近在开发跨平台应用时,客户要求添加语音输入功能以提升用户体验。经过一番调研和实践,我成功在UniApp项目中实现了语音输入与识别功能,现将过程和方法分享出来,希望对有类似需求的开发者有所帮助。
随着移动设备的普及,语音交互已成为一种高效的人机交流方式。与传统的文字输入相比,语音输入具有以下优势:
在商业应用中,语音输入可以显著降低用户的操作门槛,提高转化率和用户留存。
在UniApp环境中实现语音识别,主要有三种方案:
经过对比和测试,我最终采用了混合方案:
首先需要安装语音识别插件。我选择了市场上比较成熟的speech-baidu插件,这是基于百度语音识别SDK封装的UniApp插件。
安装插件后,在manifest.json中配置:
"app-plus": {
"plugins": {
"speech": {
"baidu": {
"appid": "你的百度语音识别AppID",
"apikey": "你的API Key",
"secretkey": "你的Secret Key"
}
}
},
"distribute": {
"android": {
"permissions": [
"" ,
""
]
}
}
}
接下来创建语音识别组件:
{{ isRecording ? '松开结束' : '按住说话' }}
正在聆听...
微信小程序提供了原生的语音识别API,使用非常方便:
// 在小程序环境下的代码
startRecord() {
// #ifdef MP-WEIXIN
this.isRecording = true;
this.startWaveAnimation();
const recorderManager = wx.getRecorderManager();
recorderManager.onStart(() => {
console.log('录音开始');
});
recorderManager.onStop((res) => {
this.isRecording = false;
this.stopWaveAnimation();
// 将录音文件发送到微信后台识别
wx.showLoading({ title: '识别中...' });
const { tempFilePath } = res;
wx.uploadFile({
url: 'https://api.weixin.qq.com/cgi-bin/media/voice/translatecontent',
filePath: tempFilePath,
name: 'media',
formData: {
access_token: this.accessToken,
format: 'mp3',
voice_id: Date.now(),
lfrom: this.lang === 'zh' ? 'zh_CN' : 'en_US',
lto: 'zh_CN'
},
success: (uploadRes) => {
wx.hideLoading();
const data = JSON.parse(uploadRes.data);
if (data.errcode === 0) {
this.$emit('result', data.result);
} else {
uni.showToast({
title: `识别失败: ${data.errmsg}`,
icon: 'none'
});
}
},
fail: () => {
wx.hideLoading();
uni.showToast({
title: '语音识别失败',
icon: 'none'
});
}
});
});
recorderManager.start({
duration: this.maxDuration * 1000,
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 48000,
format: 'mp3'
});
// #endif
},
stopRecord() {
// #ifdef MP-WEIXIN
wx.getRecorderManager().stop();
// #endif
// ...与App端相同的代码...
}
需要注意的是,微信小程序的语音识别需要获取access_token,这通常需要在后端实现并提供接口。
在H5端,我们可以利用Web Speech API来实现语音识别,当浏览器不支持时则降级为云服务API:
startRecord() {
// #ifdef H5
this.isRecording = true;
this.startWaveAnimation();
// 检查浏览器是否支持Speech Recognition
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
this.recognition = new SpeechRecognition();
this.recognition.lang = this.lang === 'zh' ? 'zh-CN' : 'en-US';
this.recognition.continuous = false;
this.recognition.interimResults = false;
this.recognition.onresult = (event) => {
const result = event.results[0][0].transcript;
this.$emit('result', result);
};
this.recognition.onerror = (event) => {
uni.showToast({
title: `识别错误: ${event.error}`,
icon: 'none'
});
};
this.recognition.onend = () => {
this.isRecording = false;
this.stopWaveAnimation();
};
this.recognition.start();
} else {
// 不支持Web Speech API,调用云服务API
this.useCloudSpeechAPI();
}
// #endif
// 设置最长录制时间
this.timer = setTimeout(() => {
if (this.isRecording) {
this.stopRecord();
}
}, this.maxDuration * 1000);
},
stopRecord() {
// #ifdef H5
if (this.recognition) {
this.recognition.stop();
}
// #endif
// ...与App端相同的代码...
},
useCloudSpeechAPI() {
// 这里实现降级方案,调用后端接口进行语音识别
uni.chooseFile({
count: 1,
type: 'file',
extension: ['.mp3', '.wav'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0];
// 上传音频文件到后端进行识别
uni.uploadFile({
url: this.apiBaseUrl + '/speech/recognize',
filePath: tempFilePath,
name: 'audio',
formData: {
lang: this.lang
},
success: (uploadRes) => {
const data = JSON.parse(uploadRes.data);
if (data.code === 0) {
this.$emit('result', data.result);
} else {
uni.showToast({
title: `识别失败: ${data.msg}`,
icon: 'none'
});
}
},
complete: () => {
this.isRecording = false;
this.stopWaveAnimation();
}
});
}
});
}
为了让调用方便,我封装了一个统一的API:
// 在 utils/speech.js 中
const Speech = {
// 开始语音识别
startRecognize(options) {
const { lang = 'zh', success, fail, complete } = options;
// #ifdef APP-PLUS
const speechPlugin = uni.requireNativePlugin('speech-baidu');
speechPlugin.start({
vadEos: 3000,
language: lang === 'zh' ? 'zh-cn' : 'en-us'
}, (res) => {
if (res.errorCode === 0) {
success && success(res.result);
} else {
fail && fail(res);
}
complete && complete();
});
return {
stop: () => speechPlugin.stop(),
cancel: () => speechPlugin.cancel()
};
// #endif
// #ifdef MP-WEIXIN
// 微信小程序实现逻辑
// ...
// #endif
// #ifdef H5
// H5实现逻辑
// ...
// #endif
}
};
export default Speech;
现在,我们来看一个实际应用场景 - 在聊天应用中添加语音输入功能:
在实际开发中,我遇到了一些需要特别注意的问题:
语音识别需要麦克风权限,不同平台的权限处理方式不同:
// 统一请求录音权限
requestAudioPermission() {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
const permissions = ['android.permission.RECORD_AUDIO'];
plus.android.requestPermissions(
permissions,
function(e) {
if (e.granted.length === permissions.length) {
resolve();
} else {
reject(new Error('未授予录音权限'));
}
},
function(e) {
reject(e);
}
);
// #endif
// #ifdef MP-WEIXIN || MP-BAIDU
uni.authorize({
scope: 'scope.record',
success: () => resolve(),
fail: (err) => reject(err)
});
// #endif
// #ifdef H5
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(() => resolve())
.catch(err => reject(err));
} else {
reject(new Error('浏览器不支持录音功能'));
}
// #endif
});
}
语音识别需要上传音频数据,在移动网络下会消耗流量:
// 检查网络环境并提示用户
checkNetwork() {
uni.getNetworkType({
success: (res) => {
if (res.networkType === '2g' || res.networkType === '3g') {
uni.showModal({
title: '流量提醒',
content: '当前处于移动网络环境,语音识别可能消耗较多流量,是否继续?',
success: (confirm) => {
if (confirm.confirm) {
this.startSpeechRecognition();
}
}
});
} else {
this.startSpeechRecognition();
}
}
});
}
长时间语音识别会增加内存和电量消耗,需要做好优化:
// 设置最大录音时长和自动结束
setupMaxDuration() {
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
if (this.isRecording) {
uni.showToast({
title: '录音时间过长,已自动结束',
icon: 'none'
});
this.stopRecord();
}
}, this.maxDuration * 1000);
}
// 空闲自动停止
setupVAD() {
// 监测静音,如果用户停止说话3秒,自动结束录音
let lastAudioLevel = 0;
let silenceCounter = 0;
this.vadTimer = setInterval(() => {
// 获取当前音量
const currentLevel = this.getAudioLevel();
if (Math.abs(currentLevel - lastAudioLevel) < 0.05) {
silenceCounter++;
if (silenceCounter > 30) { // 3秒 (30 * 100ms)
this.stopRecord();
}
} else {
silenceCounter = 0;
}
lastAudioLevel = currentLevel;
}, 100);
}
除了语音识别外,语音合成(Text-to-Speech)也是很有用的功能,可以将文本转换为语音:
// 语音合成
textToSpeech(text, options = {}) {
const { lang = 'zh', speed = 5, volume = 5 } = options;
// #ifdef APP-PLUS
const speechPlugin = uni.requireNativePlugin('speech-baidu');
return new Promise((resolve, reject) => {
speechPlugin.textToSpeech({
text,
language: lang === 'zh' ? 'zh-cn' : 'en-us',
speed,
volume
}, (res) => {
if (res.errorCode === 0) {
resolve(res);
} else {
reject(new Error(`语音合成失败: ${res.errorCode}`));
}
});
});
// #endif
// #ifdef H5
return new Promise((resolve, reject) => {
if ('speechSynthesis' in window) {
const speech = new SpeechSynthesisUtterance();
speech.text = text;
speech.lang = lang === 'zh' ? 'zh-CN' : 'en-US';
speech.rate = speed / 10;
speech.volume = volume / 10;
speech.onend = () => {
resolve();
};
speech.onerror = (err) => {
reject(err);
};
window.speechSynthesis.speak(speech);
} else {
reject(new Error('当前浏览器不支持语音合成'));
}
});
// #endif
}
开发过程中,我遇到了一些常见问题与解决方法,分享如下:
我们的解决方案是进行兼容性检测,并根据设备性能自动调整参数:
// 检测设备性能并调整参数
detectDevicePerformance() {
const platform = uni.getSystemInfoSync().platform;
const brand = uni.getSystemInfoSync().brand;
const model = uni.getSystemInfoSync().model;
// 低端安卓设备优化
if (platform === 'android') {
// 特定型号的优化
if (brand === 'samsung' && model.includes('SM-J')) {
return {
sampleRate: 8000,
quality: 'low',
useVAD: false // 禁用语音活动检测,降低CPU占用
};
}
}
// 默认配置
return {
sampleRate: 16000,
quality: 'high',
useVAD: true
};
}
通过本文,我们探讨了在UniApp中实现语音输入与识别功能的多种方案,并提供了具体的代码实现。这些实现方案已在实际项目中得到验证,能够满足大多数应用场景的需求。
语音技术在移动应用中的重要性不断提升,未来可以探索更多高级功能:
希望本文对你在UniApp中实现语音功能有所帮助!如有问题欢迎在评论区交流讨论。