⼩程序流式录⾳FrameRecorded
录⾳的API有那些?什么叫流式 & ⾮流式?
wx.stopRecord(Object object)  // 停⽌录⾳
wx.startRecord(Object object)    // 开始录⾳
RecorderManager  // 全局唯⼀的录⾳管理器 RecorderManager
前两个⾃然不⽤说了,代表的是第⼀个时代的录⾳接⼝,注意的是从基础库开始,本接⼝停⽌维护(推荐使⽤后⾯的),后两个是配套的,通过调⽤wx.getRecorderManager() 获取全局唯⼀的录⾳管理器 RecorderManager,RecorderManager是⼀个单实例,他能提供如下⽅法:
开始录⾳
暂停录⾳
继续录⾳
停⽌录⾳
监听录⾳开始事件
监听录⾳继续事件
监听录⾳暂停事件
监听录⾳结束事件
监听已录制完指定帧⼤⼩的⽂件事件。如果设置了 frameSize,则会回调此事件。
监听录⾳错误事件
监听录⾳因为受到系统占⽤⽽被中断开始事件。以下场景会触发此事件:语⾳聊天、视频聊天。此事件触发后,录⾳会被暂停。pause 事件在此事件后触发
监听录⾳中断结束事件。在收到 interruptionBegin 事件之后,⼩程序内所有录⾳会暂停,收到此事件之后才可再次录⾳成功。
这些api,都是RecorderManager实例对象的⽅法,on开头的都是事件回调函数,其他的就是对Recorde
rManager的操作⽅法,那么流式和⾮流式如何体现呢?⾸先说⼀下⼆者的本质区别:
⾮流式:也可以理解为整包上传,即有明确的开始和结束,也就是⼀个完整的⾳频⽂件,我们要等⾳频结束后在上传或处理⽂件。这样做的好处在于实现成本低,后台接收到完整⽂件后就可以直接处理了。前端交互页⽐较好处理。但是⼀旦录⾳时间⽐较长,⽂件就很⼤,上传时就有可能超过请求报⽂的⼤⼩限制,后台接收了⼤⽂件,处理时间页会⽐较长,会感觉接⼝响应超时,或⽐较慢,不适合实时性要求较⾼的场景。但较短的⾳频⽐较适合⾮流式
流式:是不等⾳频结束,⼀旦采集⾳频满⾜⼀定的帧⼤⼩后就返回这⼀段的⾳频(具体每⼀段是不是完整⽂件,要看系统平台)。我们在监听函数中拿到这⼀段的⾳频,就马上上传或处理-》得到处理结果,马上呈现到页⾯上,这样⼀边采集,⼀边处理,⼀边呈现结果。实时效果⽐较好。后台每次接受的⽂件⼤⼩也不会太⼤,处理速度也⽐较有保证。但是⼀⽅⾯前端交互⽐之前复杂,另⼀⽅⾯后台逻辑要能够处理排序逻辑(保证接收的顺序)。⽐较适合较长⾳频的场景。
两种⽅式,没有好坏之分,只有使⽤场景的不同,需要结合⾃⾝业务选择,下⾯来讲⼀下如何⽤的api来实现⾮流式和流式录⾳。
⾮流式如何实现录⾳功能
既然是获取⼀段完整的⾳频⽂件,那么就需要有明确的开始和结束,可以参考这种写法:
1 wx.startRecord({
2  success (res) {
3    const tempFilePath = pFilePath
4  }
5 })
6 setTimeout(function () {
7  wx.stopRecord() // 结束录⾳
8 }, 10000)
注意2点,这⾥返回的是startRecord 的回调函数;返回的是⼀个临时⽂件路径。如果想上传到后台处理,还需要调⽤的上传⽂件的接⼝:
wx.uploadFile(Object object)
1 wx.startRecord({
2  success (res) {
3    const tempFilePath = pFilePath
4    wx.uploadFile({
5      url: 'example.weixin.qq/upload', //仅为⽰例,⾮真实的接⼝地址
6      filePath: tempFilePaths[0],
7      name: 'file',
8      formData: {
9        'user': 'test'
10      },
11      success (res){
12        const data = res.data
13//do something
14      }
15    })
16  }
17 })
18 setTimeout(function () {
19  wx.stopRecord() // 结束录⾳
20 }, 10000)
如果你使⽤新版的接⼝(RecorderManager)的话,⾳频⽂件的获取:并不是在“start”⾥⾯⽽是“end”:Stop(function callback)
监听录⾳结束事件
参数
function callback
录⾳结束事件的回调函数
参数
Object res
属性类型说明
tempFilePath string录⾳⽂件的临时路径
duration number录⾳总时长,单位:ms
fileSize number录⾳⽂件⼤⼩,单位:Byte
也就是说⽂件是在回调函数的tempFilePath参数中,其他的也都同理。
流式如何实现录⾳功能
由于⾳频的获取是不断采集,每采集够⼀定⾳频帧数,就可以进⾏⾳频处理,所以⾳频的数据回调并不依赖明确的停⽌事件,⽽是满⾜这⼏点:
在RecorderManager.start时配置好frameSize(也就是每⼀个分⽚的帧⼤⼩)指定帧⼤⼩,单位 KB。传⼊ frameSize 后,每录制指定帧⼤⼩的内容后,会回调录制的⽂件内容,不指定则不会回调。暂仅⽀持 mp3 格式。
因此format也要设置为mp3
按照你和后台的约定,把采样率、录⾳通道数、编码码率设置好
最后在 FrameRecorded(function callback) 的回调中获取数据frameBuffer 和是否为最后⼀帧isLastFrame 也就是FrameRecorded(function callback)
监听已录制完指定帧⼤⼩的⽂件事件。如果设置了 frameSize,则会回调此事件。
参数
function callback
已录制完指定帧⼤⼩的⽂件事件的回调函数
参数
Object res
属性类型说明
frameBuffer ArrayBuffer录⾳分⽚数据
isLastFrame boolean当前帧是否正常录⾳结束前的最后⼀帧
1 let seq = 0
FrameRecorded(res => {
3  const {frameBuffer, isLastFrame} = res
4  const base64Buffer = wepy.arrayBufferToBase64(frameBuffer)
5  let url
6  const data = {
7    miniappname: 'fanyijun'
8  }
9  url = `${你的域名}/recorder/xxxxxx`
10  data.isEnd = isLastFrame ? 1 : 0
11  data.lang = recordObj.lang
12  data.sessionUuid = recordObj.sessionUuid
13  data.audio = base64Buffer
14  data.seq = seq
15// console.log('recorderFrame url: ', url)
16  console.log('recorderFrame data: ', data)
前端大文件上传解决方案
17  quest({
18    url,
19    data,
20    method: 'POST'
21  }).then(res => {
22// 处理逻辑
23  }).catch(e => {
24    console.log('err:', e)
25  })
26  seq++
27 })
这样,我们就将每次监听到的⾳频分⽚都上传到服务器了,注意流式接⼝返回的不是⾳频⽂件的临时地址,⽽是frameBuffer,因为他不是完整的⽂件,本地也不会存储这个⽂件。
两种⽅式的区别在哪⾥
调⽤风格⽂件⼤⼩返回值⾳频格式
⾮流式录⾳接⼝stop后在回调中拿到完整的⽂件根据实际录⾳时长(start和end的间
隔)决定
临时⽂件本地地址,徐调⽤上
传接⼝
mp3、aac、
silk
流式录⾳接⼝在onFrameRecordedzh中不断获取分⽚,持续获
取,持续处理
根据设定分⽚⼤⼩决定frameBuffer,可普通请求上传mp3
⾮流式的分⽚到底是什么?
刚刚说到了分⽚,那么分⽚到底是什么,是不是独⽴的⽂件,能不能单独处理?我们⼀起来看⼀下:
⾸先我在内⽹咨询了⼩程序这边的同事,得到的反馈是流式分⽚返回的是frameBuffer,不能独⽴使⽤。我同时也⾃⼰做了实验验证了⼀下。那就是那每⼀⽚frameBuffer,拿出来,分别写⼊⽂件查看,因为Buffer是⼀种⼆进制数据类型,我们直接在node中就可以保存⽂件,因此我将⼀次录⾳的相邻的⾳频分⽚分别写⼊:
1var fs = require('fs');
2var file = require('./file');
3
4var data = file.data;
5
6var now = w(); //获取系统当前时间数值
7var savePath = './' + now + '.mp3'; //服务器存储⽂件名
8 let dataBuffer = new Buffer(data, 'base64');
9 console.log(`data.length ${data.length}`);
10 console.log(`dataBuffer: leng ${dataBuffer.byteLength}`);
11
12 fs.writeFile(savePath, aaa, function(err) {
13if (err) {
14    console.log(err);
15  }
16 });
发现每个⽂件都可以独⽴的播放,那么我们来试⼀下把这些frameBuffer链接在⼀起呢:
1var fs = require('fs');
2var file = require('./file');
3
4var datas = file.datas;
5
6var now = w(); //获取系统当前时间数值
7var savePath = './' + now + '.mp3'; //服务器存储⽂件名
8// let dataBuffer = new Buffer(data, 'base64');
9// console.log(`data.length ${data.length}`);
10// console.log(`dataBuffer: leng ${dataBuffer.byteLength}`);
11
12 let aaa = at(
13  datas.map(x => {
14return new Buffer(x, 'base64');
15  })
16 );
17
18 fs.writeFile(savePath, aaa, function(err) {
19if (err) {
20    console.log(err);
21  }
22 });
发现这个链接frameBuffer后的⾳频播放出来就是我说的内容,⽂件⼤⼩也符合。但是为什么不建议后台直接使⽤呢?
“分⽚分别转码在合并” vs 合并后再转码的MD5,并不相同(⽂件⼤⼩和长度相等),因此不能看作是同⼀⾳频,后台同事和我的解释就是会引⼊噪声,⽬前来看仍然需要之前说的排序⽅案。