通过录音管理器 RecorderManager调用手机的录音功能实现音频的在线采集,通过采集到的音频的base64字符串调用云开发侧实现的腾讯云一句话识别云函数,然后将识别结果回调到小程序页面中。
添加描述
添加描述
添加描述
在asr云函数目录上右键选择在"在终端中打开"
E:\tencentcloudcode\wechat\functions\asr>npm install
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
> protobufjs@6.8.8 postinstall E:\tencentcloudcode\wechat\functions\asr\node_modules\protobufjs
> node scripts/postinstall
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN asr@1.0.0 No description
npm WARN asr@1.0.0 No repository field.
added 101 packages from 194 contributors and audited 186 packages in 8.85s
found 0 vulnerabilities
// 云函数入口文件
const cloud = require('wx-server-sdk') // 引入云开发服务的内核SDK
cloud.init( //初始化一个'wx-server-sdk' SDK 实例
{
env: 'ai-test-t7t64' // 开通云开发服务后创建的云环境的环境ID(默认可以创建两个ID)
}
)
// 云函数入口函数
exports.main = async (event, context) => {
const tencentcloud = require("tencentcloud-sdk-nodejs"); //引入腾讯云SDK
// 下面的代码可以通过explorer在线生成(https://console.cloud.tencent.com/api/explorer?Product=aai&Version=2018-05-22&Action=SentenceRecognition&SignVersion=)
const AaiClient = tencentcloud.aai.v20180522.Client;
const models = tencentcloud.aai.v20180522.Models;
const Credential = tencentcloud.common.Credential;
const ClientProfile = tencentcloud.common.ClientProfile;
const HttpProfile = tencentcloud.common.HttpProfile;
let cred = new Credential("", "");
let httpProfile = new HttpProfile();
httpProfile.endpoint = "aai.tencentcloudapi.com";
let clientProfile = new ClientProfile();
clientProfile.httpProfile = httpProfile;
let client = new AaiClient(cred, "ap-guangzhou", clientProfile);
let req = new models.SentenceRecognitionRequest();
let base64Data=event.x //接收客户端post的x参数,值类型为base64字符串
let DataLen = event.s //接收音频文件的大小
var params = {"ProjectId":0,"SubServiceType":2,"EngSerViceType":"16k_zh","SourceType":1,"VoiceFormat":"mp3","UsrAudioKey":"www","Data":base64Data,"DataLen":DataLen} // 定义SDK的请求参数字典
params = JSON.stringify(params) // 转换为json字符串
req.from_json_string(params);
return new Promise((resolve, reject) => { // 通过Promise容器来接收异步API的回调,然后通过当前脚本返回给客户端
client.SentenceRecognition(req, function(errMsg, response) { // 此接口是异步的,那么当前脚本无法对外直接访问接口返回值
if (errMsg) {
resolve({ "Result": errMsg })
}
// resp = response.to_json_string()
resolve({ "Result": response})
});
})
}
注:云函数的入口文件index.js中调用的"一句话识别"API方法"SentenceRecognition”是异步的,如果直接拷贝Explorer中生成的Demo,将无法为小程序客户端返回"SentenceRecognition”的回调数据,脚本最终会返回null;所以这里我们需要使用Promise对象来获取"SentenceRecognition"的回调数据,然后返回给小程序客户端
在小程序公共配置文件app.json中,添加页面生成参数
"pages/voicec/voicec",
点击"编译"生成页面目录及页面
<!--pages/voicec/voicec.wxml-->
<view class="REC">
<view class="time">{{status==0?'录音时长':(status==3?'录音结束':'录音中')}}:{{time}} 秒 ({{duration/1000}}秒)</view>
<view class="rin">
<view class="{{status==3 && actionStatus==0?'show':'hide'}}" bindtap="play" hover-class="skip">{{actionStatus==1?'播放中':'播放录音'}}</view>
<view class="{{status==3?'show':'hide'}}" bindtap="again" hover-class="skip">再次录制</view>
</view>
<view class="anniu">
<view class="{{status==0?'highlight':'gray'}}" bindtap="start" hover-class="skip">开始</view>
<view class="{{status==1?'highlight':'gray'}}" bindtap="stop" hover-class="skip">暂停</view>
<view class="{{status==2?'highlight':'gray'}}" bindtap="continue" hover-class="skip">继续</view>
<view class="{{(status==1 || status==2)?'highlight':'gray'}}" bindtap="shutoff" hover-class="skip">停止</view>
<view class="{{status==3?'highlight':'gray'}}" bindtap="recognition" hover-class="skip">识别</view>
</view>
<view class="progress">
<progress percent="{{time*(100/(duration/1000))}}" stroke-width="10" backgroundColor="#fff" border-radius="15" stroke-width="4" color="#7FFF00" active />
</view>
</view>
<view class=".REC">
<textarea placeholder="录音完成后点击识别可将音频转文字" auto-focus value="{{ Words }}" />
</view>
使用的组件:
使用的视图容器:
使用的XML语法:
使用的视图层:
// pages/voicec/voicec.js
const recorderManager = wx.getRecorderManager() // 获取全局唯一的录音管理器 RecorderManager
const innerAudioContext = wx.createInnerAudioContext() // 创建内部 audio 上下文 InnerAudioContext 对象。
var init // 声明一个全局变量,let为局部变量
Page({ // 使用Page函数作为Page构造器来注册一个页面
/**
* 页面的初始数据
*/
data: {
voiceSize:0, // 音频的大小
time: 0, // 初始时间
duration: 60000, // 录音长时间为1分钟
localFilePath: "", //录音文件在本地的路径
status: 0, // 录音器的状态:开始1,暂停2,继续1,停止3
actionStatus: 0, //录音播放状态,1为播放状态,0为未播放状态
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
},
/**开始录音 */
start: function() {
clearInterval(init) // 取消之前的计时
recorderManager.onStart((res) => { // 监听录音开始事件
console.log('开始录音')
this.setData({
status: 1 // 录音开始状态为1
})
})
recorderManager.onStop((res) => { // 监听录音停止事件
console.log('停止录音', res)
this.setData({
tempFilePath: res.tempFilePath, // 如果录音停止了,修改本地文件地址
status: 3
})
this.timeCounter(this.data.time) // 取消计时
})
const options = { //定义录音参数
duration: this.data.duration, // 录音时长
format: 'mp3', // 音频格式
}
this.timeCounter() // 开始计时
recorderManager.start(options) // 开始录音
},
/**
* 录音暂停
*/
stop: function() {
recorderManager.onPause(() => {
console.log('recorder pause')
this.setData({
status: 2
})
})
this.timeCounter(this.data.time) // 取消计时,暂时和停止都是取消计时
recorderManager.pause() // 暂停录音
},
/**
* 录音继续
*/
continue: function() {
this.setData({
status: 1 // 标记为正在录音状态
})
this.timeCounter() // 在之前的计时基础上继续+1计时
recorderManager.resume() // 继续录音
},
/**
* 录音停止
*/
shutoff: function() {
recorderManager.onStop((res) => {
console.log('recorder stop', res)
this.setData({
tempFilePath: res.tempFilePath, // 录音生成文件的本地路径
status: 3 // 标记录音状态为停止
})
})
this.timeCounter(this.data.time) // 取消计时
recorderManager.stop() // 停止录音
},
/**
* 录音识别
*/
recognition: function() {
var that=this;
wx.getFileInfo({
filePath:this.data.tempFilePath,
success (res) {
console.log("录音文件的大小为"+res.size)
that.data.voiceSize=res.size
}
})
wx.cloud.init() // 初始化云函数环境
wx.cloud.callFunction({ // 调用云函数
// 云函数名称
name: 'asr', // 调用的云函数的名称
// 传给云函数的参数
data: {
s:that.data.voiceSize, // 音频文件的大小
// x: wx.getFileSystemManager().readFileSync(this.data.tempFilePath, 'base64')
x: wx.getFileSystemManager().readFileSync("files/test.mp3", 'base64') // 读取本地文件的base64字符串
},
success: function(res) {
console.log(res)
that.setData({ // 发送数据到视图层
Words: res.result.Result.Result
})
},
fail: console.error
})
},
/**
* 录音播放
*/
play: function() {
innerAudioContext.src = this.data.tempFilePath // 音频资源的地址,用于直接播放
innerAudioContext.obeyMuteSwitch = false // 是否遵循系统静音开关,默认为 true,当此参数为 false 时,即使用户打开了静音开关,也能继续发出声音
if (this.data.actionStatus == 0) {
this.setData({
actionStatus: 1
})
innerAudioContext.play() // 播放音频
}
innerAudioContext.onEnded(() => { //监听音频自然播放至结束的事件
innerAudioContext.stop() // 停止播放
this.setData({
actionStatus: 0
})
})
},
timeCounter: function(time) { // 定义一个计时器函数
var that = this
if (time == undefined) {
init = setInterval(function() { // 设定一个计时器ID。按照指定的周期(以毫秒计)来执行注册的回调函数
var time = that.data.time + 1; // 每秒钟计时+1
that.setData({
time: time
})
}, 1000);
} else {
clearInterval(init) // 取消计时
console.log("暂停计时")
}
},
/**
* 重新录制
*/
again: function() {
var that = this
wx.showModal({ // 显示模态对话框
title: "重新录音", //提示的标题
content: "是否重新录制?", //提示的内容
success(res) {
if (res.confirm) { // 点击了确定
that.setData({ // 重置初始化数据
time: 0,
tempFilePath: "",
status: 0,
actionStatus: 0
})
innerAudioContext.stop() // 停止音频
}
}
})
}
})
使用到的知识点: Page 构造器
文件管理器FileSystemManager读取指定编码的文件内容
注意:如果自定义函数中嵌套了wx等对象函数,数据传递应该先声明"var that=this",然后再嵌套函数,如wx.request中使用"that.setData"来传递数据
{
"navigationBarTitleText": "云开发一句话识别在线测试",
"backgroundColor": "#eeeeee"
}
/* pages/voicec/voicec.wxss */
.REC {
border-radius: 25rpx;
background-color: rgb( 199,237,204 );
padding: 6rpx 0rpx;
margin: 15rpx 35rpx;
}
.rin {
justify-content: space-between;
align-items: center;
margin: 0rpx 120rpx;
display: flex;
}
.rin .show {
background-color: rgb(178, 228, 228);
padding: 15rpx;
width: 210rpx;
border: 5rpx solid rgb(127, 204, 214);
border-radius: 20rpx;
font-size: 28rpx;
display: flex;
justify-content: center;
align-items: center;
}
.rin .hide {
padding: 15rpx;
align-items: center;
border-radius: 20rpx;
display: flex;
width: 215rpx;
font-size: 28rpx;
justify-content: center;
border: 5rpx solid #eee;
pointer-events: none;
background-color: rgba(137, 190, 178, 0.445);
}
.time {
text-align: center;
line-height: 75rpx;
font-size: 28rpx;
}
.progress {
margin: 25rpx;
}
.play {
margin: 0rpx 25rpx;
}
.content {
line-height: 60rpx;
font-size: 28rpx;
display: flex;
justify-content: center;
}
.anniu {
display: flex;
margin: 10rpx 50rpx;
justify-content: space-between;
}
.highlight {
display: flex;
font-size: 28rpx;
width: 80rpx;
height: 80rpx;
justify-content: center;
border-radius: 50%;
align-items: center;
background-color: rgb(107, 194, 53);
border: 5rpx solid rgb(127, 204, 214);
}
.skip {
transform: scale(0.9);
}
.anniu .gray {
pointer-events: none;
background-color: rgba(137, 190, 178, 0.445);
display: flex;
width: 80rpx;
height: 80rpx;
font-size: 28rpx;
justify-content: center;
align-items: center;
border-radius: 50%;
border: 5rpx solid rgb(241, 244, 245);
}
测试效果
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。