一、实现方式
通过录音管理器 RecorderManager调用手机的录音功能实现音频的在线采集,通过采集到的音频的base64字符串调用云开发侧实现的腾讯云一句话识别云函数,然后将识别结果回调到小程序页面中。
二、实现流程
第一步:开通云开发控制台并创建云端项目环境
添加描述
添加描述
添加描述
第二步:在小程序项目根目录下创建本地云函数根目录functions,在项目根目录找到 project.config.json 文件,新增 cloudfunctionRoot 字段,值为刚才创建的本地云函数根目录名称
第三步:创建一句话识别云函数并配置tencentcloud-sdk-nodejs依赖
第四步:安装依赖
在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
第五步:在一句话识别云函数目录下的入口文件index.js中实现一句话识别的API调用Demo,然后上传Demo至云端
// 云函数入口文件 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",
点击"编译"生成页面目录及页面
voicec.wxml
<!--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>
使用的组件:
进度条progress
多行输入框textarea
使用的视图容器:
view
使用的XML语法:
双大括号数据绑定之三元运算
使用的视图层:
bindtap事件绑定
voicec.js
// 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 构造器
录音管理器
HTTPS 网络请求
文件管理器FileSystemManager读取指定编码的文件内容
数据传递setData
注意:如果自定义函数中嵌套了wx等对象函数,数据传递应该先声明"var that=this",然后再嵌套函数,如wx.request中使用"that.setData"来传递数据
voicec.json
{ "navigationBarTitleText": "云开发一句话识别在线测试", "backgroundColor": "#eeeeee" }
全局配置
voicec.wxss
/* 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); }
WXSS样式学习
测试效果
留言评论
暂无留言