一、效果展示
我们有一个按钮,点击"开始录音"按钮,此时按钮变成"停止录音"并开始计时,点击停止录音后,界面上即可展示返回的文字
二、代码实现
完整代码实现见github
1.小程序端代码
js
// index.js
const recorderManager = wx.getRecorderManager();
Page({
data: {
recordState: false, // 录音状态
recordTime: 0, // 录音时长
voices: [], // 语音消息列表
},
// 开始录音
startRecord() {
const options = {
duration: 60000, // 最长录音时间,单位ms
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 48000,
format: 'mp3',
};
recorderManager.start(options);
this.setData({ recordState: true, recordTime: 0 });
this.startTimer();
recorderManager.onStart(() => {
console.log('recorder start');
});
recorderManager.onError((res) => {
console.error('recorder error:', res);
});
},
// 停止录音
stopRecord() {
recorderManager.stop();
this.setData({ recordState: false });
this.clearTimer();
recorderManager.onStop((res) => {
console.log('recorder stop', res);
this.uploadVoice(res.tempFilePath);
});
},
// 上传语音文件并转换为文本
uploadVoice(filePath) {
wx.showLoading({ title: '识别中...' });
wx.uploadFile({
url: 'http://localhost:3000/upload', // 替换为你的服务器地址
filePath: filePath,
name: 'file',
success: (res) => {
wx.hideLoading();
const data = JSON.parse(res.data);
if (data.text) {
this.setData({
voices: [...this.data.voices, { type: 'text', content: data.text }]
});
}
},
fail: (error) => {
wx.hideLoading();
console.error('Upload failed', error);
wx.showToast({
title: '上传失败',
icon: 'none'
});
}
});
},
// 开始计时器
startTimer() {
this.timer = setInterval(() => {
this.setData({ recordTime: this.data.recordTime + 1 });
}, 1000);
},
// 清除计时器
clearTimer() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
// 组件生命周期函数
onUnload() {
this.clearTimer();
}
});
2.服务端代码
js
const path = require('path');
const fs = require('fs');
const express = require('express');
const axios = require('axios');
const multer = require('multer');
const FormData = require('form-data');
require('dotenv').config();
const { APP_ID, APP_SECRET, PORT } = process.env
// 确保 uploads 目录存在
const uploadsDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir);
}
const app = express();
// 替换为你的 AppID 和 AppSecret
const appId = APP_ID;
const appSecret = APP_SECRET;
// 配置 multer 来处理文件上传
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
}
});
const upload = multer({ storage: storage });
// 获取 ACCESS_TOKEN 的函数
async function getAccessToken() {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`;
try {
const response = await axios.get(url);
console.log('Access token response:', response.data);
if (response.data.access_token) {
return response.data.access_token;
} else {
throw new Error('Failed to get access token');
}
} catch (error) {
console.error('Error getting access token:', error);
return null;
}
}
// 调用微信语音识别接口
async function recognizeSpeech(accessToken, filePath) {
try {
console.log('Reading file:', filePath);
// const fileContent = fs.readFileSync(filePath);
// const base64Audio = fileContent.toString('base64');
const form = new FormData();
form.append('media', fs.createReadStream(filePath));
const voice_id = Date.now().toString();
const url = `https://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=${accessToken}&format=mp3&voice_id=${voice_id}&lang=zh_CN`
console.log('Calling WeChat API...', url);
const response = await axios.post(url,
form,
{
headers: form.getHeaders(),
}
);
console.log('WeChat API response:', response.data);
if (response.data.errcode) {
throw new Error(`WeChat API error: ${response.data.errmsg}`);
}
const queryRecoresultUrl = `https://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=${accessToken}&voice_id=${voice_id}&lang=zh_CN`
const res = await axios.post(queryRecoresultUrl,
{},
{
headers: { 'Content-Type': 'application/json' }
}
);
console.log('xxxxx', res.data)
return res.data.result;
} catch (error) {
console.error('Error recognizing speech:', error);
throw error;
}
}
// 处理语音文件上传和识别
app.post('/upload', upload.single('file'), async (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
console.log('File uploaded:', req.file);
try {
let accessToken;
let recognitionResult;
let retries = 1;
while (retries > 0) {
try {
accessToken = await getAccessToken();
console.log('Got access token:', accessToken);
recognitionResult = await recognizeSpeech(accessToken, `uploads/0.mp3`);
console.log('Recognition result:', recognitionResult);
break;
} catch (error) {
console.error(`Attempt failed, retries left: ${retries - 1}`, error);
retries--;
if (retries === 0) throw error;
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒后重试
}
}
// 删除临时文件
fs.unlinkSync(req.file.path);
res.json({ text: recognitionResult });
} catch (error) {
console.error('Error:', error);
res.status(500).send('Server error: ' + error.message);
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
三、返回示例
四、遗留问题
可能由于个人水平问题,有以后遗留问题,如果大家解决方案或问题,欢迎随时交流
- 此代码在服务端写死了一个待转换的mp3文件,因为开发环境本底录音无法试听
- 录音不知道什么原因,可能会有一半不会被翻译,暂时没有找到解决方案
- 多次上传同一个录音后,会返回空的转换结果