小程序 RecorderManager 录音分贝解析及来电检测

小程序踩坑系列(一)

录音API简介

  • 通过 wx.getRecorderManager() 获取全局唯一的录音管理器。
  • 支持的事件监听包括录音开始、暂停、继续、结束、错误、帧录制完成,以及录音中断的开始和结束。

分贝解析

录音格式format需要指定为pcm

开始录音API RecorderManager.start(Object object) 可指定录音文件的格式,默认格式为aac

重点:需要指定format为pcm(无损格式)

  1. 'aac' 格式 :
    • 压缩格式: AAC(Advanced Audio Codec)是一种有损压缩格式,主要用于减少音频文件的大小。
    • 编码特性: 由于 AAC 是一种压缩格式,它在编码过程中会丢失一些音频信息,包括可能用于计算分贝的精细音频数据。
    • 分贝解析: 因为 AAC 压缩会丢失一些音频数据,因此无法直接从 AAC 文件中提取出精确的分贝信息。
  2. 'pcm' 格式 :
    • 未压缩格式: PCM(Pulse Code Modulation)是一种未压缩的音频格式,保留了音频信号的完整信息。
    • 编码特性: PCM 直接记录了音频信号的波形数据,保留了所有的音频细节。
    • 分贝解析: 由于 PCM 保留了完整的音频数据,可以直接访问波形数据,从而能够准确计算和解析分贝信息。

总结来说,AAC 格式由于其压缩特性,会丢失一些用于解析分贝的音频细节,而 PCM 格式保留了完整的音频信息,因此可以进行分贝解析。对于需要分析音频特征(如分贝、频谱等)的应用场景,PCM 是更合适的选择。

获取录音分片

RecorderManager.onFrameRecorded(function listener)

监听已录制完指定帧大小的文件事件。如果设置了 frameSize,则会回调此事件。

参数

function listener

已录制完指定帧大小的文件事件的监听函数

监听函数参数

Object res

属性 类型 说明
frameBuffer ArrayBuffer 录音分片数据
isLastFrame boolean 当前帧是否正常录音结束前的最后一帧

当我们使用这个API注册了监听函数之后了,小程序会在录音开始后,每录满指定的文件大小,就触发一次此监听,将这一段录制内容的分片数据作为入参回传给我们。

这个【指定的文件大小】,是在每次调用RecorderManager.start(Object object)时,作为可选配置项的frameSize传入的。

一个简单的示例:

jsx 复制代码
// 其他属性先忽略,这里我们重点关注frameSize
const options = {
  duration: 60000 * 3,
  sampleRate: 44100,
  numberOfChannels: 1,
  encodeBitRate: 192000,
  format: 'pcm',
  frameSize: 50 // 每帧音频数据的大小
}

const recorderManager = wx.getRecorderManager();

recorderManager.onFrameRecorded((res) => {
  const {
    frameBuffer
  } = res
  console.log('frameBuffer.byteLength', frameBuffer.byteLength)
  

  const audioData = new Int16Array(frameBuffer); // 将音频数据转换为 16 位整数
  // 计算 RMS 值
  let sum = 0;
  for (let i = 0; i < audioData.length; i++) {
    sum += audioData[i] * audioData[i]; // 平方和
  }
  const rms = Math.sqrt(sum / audioData.length); // 计算 RMS
  // 将 RMS 转换为分贝
  const db = 20 * Math.log10(rms);
  console.log('当前音量(分贝):', db);
  return db;

})

// 开始录制
recorderManager.start(options);

分贝解析算法

拿到每一帧的录音文件数据后,我们就可以对其进行解析了。

具体的计算原理就不展开解释了(实则我也不懂),网上可以搜到的算法大差不差都能用,自己多试试就行,我这边给出一个我们项目实际在用的分贝解析算法以供参考。

jsx 复制代码
// 解析每一帧的buffer并返回当前片段的分贝
const getDb = (frameBuffer) => {
  const audioData = new Int16Array(frameBuffer); // 将音频数据转换为 16 位整数
  // 计算 RMS 值
  let sum = 0;
  for (let i = 0; i < audioData.length; i++) {
    sum += audioData[i] * audioData[i]; // 平方和
  }
  const rms = Math.sqrt(sum / audioData.length); // 计算 RMS
  // 将 RMS 转换为分贝
  const db = 20 * Math.log10(rms);
  console.log('当前音量(分贝):', db);
  return db;
}

完整录音&分贝解析代码

综合以上的录音配置、帧回调和分贝解析算法,我们总结一下,完整的代码如下

jsx 复制代码
// 解析每一帧的buffer并返回当前片段的分贝
const getDb = (frameBuffer) => {
  const audioData = new Int16Array(frameBuffer); // 将音频数据转换为 16 位整数
  // 计算 RMS 值
  let sum = 0;
  for (let i = 0; i < audioData.length; i++) {
    sum += audioData[i] * audioData[i]; // 平方和
  }
  const rms = Math.sqrt(sum / audioData.length); // 计算 RMS
  // 将 RMS 转换为分贝
  const db = 20 * Math.log10(rms);
  console.log('当前音量(分贝):', db);
  return db;
}

const options = {
  duration: 60000 * 3, // 录音时长,单位 ms
  sampleRate: 44100, // 采样率
  numberOfChannels: 1, // 录音通道数
  encodeBitRate: 192000, // 编码码率
  format: 'pcm', // 音频格式
  frameSize: 50 // 每帧音频数据的大小
}

const recorderManager = wx.getRecorderManager()

recorderManager.onFrameRecorded((res) => {
  const {
    frameBuffer
  } = res
  console.log('frameBuffer.byteLength', frameBuffer.byteLength)
  
  const db = getDb(frameBuffer)
	console.log('当前音量(分贝):', db);
})

// 开始录制
recorderManager.start(options);

来电检测 & 异常处理

小程序录音动作会被各种异常场景暂停或者中断,例如接入电话、隐藏小程序、下拉通知界面等等。我们需要根据业务场景,对这些异常边界进行恰当的处理。

方法一:强制中断

适用场景:对最终的录音文件完整性、连贯性要求较高

核心逻辑:监听onPause事件,强制中断当前录音并提示重新开始

先上代码

jsx 复制代码
const recorderManager = wx.getRecorderManager();

recorderManager.onPause(() => {
	// 检测到录音被意外暂停,说明可能出现来电、隐藏小程序等边界场景
	// 直接强制结束本次录音
	recorderManager.stop();
	// 提示用户麦克风被占用,需要重新录音
	wx.showToast({
    title: '克风被占用,需要重新录音',
    duration: 2000,
  });
})

有的朋友可能会有疑问,这个逻辑怎么这么简单粗暴,我只能说,这都是经历无数边界场景折磨之后得出的最优解。基本上,只要出现意外的暂停,很难保证用户的录音能正常恢复,即使恢复了,最终拿到的录音文件,也很可能是不连续的,或者存在其他无法预知的问题。

对于录音文件完整性要求高的业务场景,直接强制结束这次录音,让用户重新录制是最好的选择。

这个方案的难点在于如何说服业务和产品(逃

方法二:暂停&恢复

适用场景:对于录音的完整性要求没有那么高,或者只需要在录音过程中进行实时分析,不需要最终的录音文件

核心逻辑:在录音被暂停、恢复时给用户提示,不进行主动的干预,重点在于让用户知道自己当前是否处于正常录音状态

小程序录音API自带微信语音/视频来电的监听功能,按照一个常见的接电话然后挂断场景下,触发监听的顺序:

onInterruptionBegin:监听录音因为受到系统占用而被中断开始事件。以下场景会触发此事件:微信语音聊天、微信视频聊天。此事件触发后,录音会被暂停。pause 事件在此事件后触发

onPause:监听录音暂停事件

onInterruptionEnd:监听录音中断结束事件。在收到 interruptionBegin 事件之后,小程序内所有录音会暂停,收到此事件之后才可再次录音成功

onResume:监听录音继续事件

要注意onInterruptionBegin / onInterruptionEnd 这对API仅在微信语音和视频的来电时会触发,如果是移动电话接入,是不会有反应的。

并且,如果移动电话接入,而用户又没有连接Wi-Fi,则大概率在来电的时候用户的手机会进入断网状态。即使用户直接挂断了电话,对于小程序来说,网络恢复也需要几秒到几十秒不等的时间。这段时间内,不但网络请求可能发送失败,小程序内置的一些API、组件初始化也可能会失败(微信小程序严重依赖网络能力)。

注意事项

兜底处理

小程序开发中,任何关键的API调用都需要有完整的兜底逻辑。

要做好这样的准备:

当你调用某个API时,它可能会直接失败;

如果这个API是异步的,它的所有回调(success、fail、complete)都有可能不会触发

清除监听

开头我们提到过,小程序的录音管理器RecorderManager全局唯一的,也就是说,我们注册过的监听,无论在哪个页面使用录音都会被触发。如果监听里有一些副作用,在其他页面执行了,可能导致意想不到的结果。

所以最好在每个使用RecorderManager的页面销毁时,清除注册的监听。

由于RecorderManager的监听API并未显式地提供对应的取消监听API(例如有onFrameRecorded ,但是没有offFrameRecorded),因此目前比较可行的清除监听的方式,是用一个空函数去覆盖原有的监听:

jsx 复制代码
Page({
  onLoad() {
    this.recorderManager = wx.getRecorderManager();

    // 注册监听器
    this.recorderManager.onFrameRecorded(this.onFrameRecorded);
    this.recorderManager.onPause(this.onPause);
  },

  onFrameRecorded(res) {
    const { frameBuffer } = res;
    const audioData = new Int16Array(frameBuffer);
    let sum = 0;
    for (let i = 0; i < audioData.length; i++) {
      sum += audioData[i] * audioData[i];
    }
    const rms = Math.sqrt(sum / audioData.length);
    const db = 20 * Math.log10(rms);
    console.log('当前音量(分贝):', db);
  },

  onPause() {
    console.warn('录音被暂停');
    wx.showToast({
      title: '录音被中断',
      icon: 'none'
    });
  },

  onUnload() {
    // 用空函数覆盖监听,达到"取消监听"的目的
    this.recorderManager.onFrameRecorded(() => {});
    this.recorderManager.onPause(() => {});
    console.log('监听已清除');
  }
});
相关推荐
橘 日向4 分钟前
uniApp开发微信小程序-连接蓝牙连接打印机上岸!
微信小程序·uni-app·notepad++
code袁1 小时前
基于微信小程序的志愿服务系统的设计与实现
微信小程序·小程序·notepad++·课程设计·小程序开发·志愿服务小程序
声知视界3 小时前
音视频基础能力之 iOS 视频篇(六):使用Metal进行视频渲染
ios·音视频开发
2401_897930065 小时前
微信小程序运行机制详解
微信小程序·小程序
『 时光荏苒 』14 小时前
微信小程序实时日志记录-接口监控
微信小程序·小程序·微信小程序日志·日志抓取
老李不敲代码14 小时前
榕壹云外卖跑腿系统:基于Spring Boot+MySQL+UniApp的智慧生活服务平台
spring boot·mysql·微信小程序·uni-app·软件需求
社会底层无业大学生16 小时前
微信小程序跳
微信小程序·小程序·notepad++
ace_TiAmo18 小时前
React8+taro开发微信小程序,实现lottie动画
微信小程序·小程序·react·taro
老李不敲代码20 小时前
榕壹云在线商城系统:基于THinkPHP+ Mysql+UniApp全端适配、高效部署的电商解决方案
mysql·微信小程序·小程序·uni-app·软件需求