TRTC Web端 仿腾讯会议麦克风静音检测

项目背景

目前 Web 的 TRTC 没有静音检测,在关闭麦克风的情况下发言没有提示,有时候会有比较尴尬的会议场景出现,为提升用户体验,这里尝试将腾讯会议的解决思路引入。

当用户在关闭麦克风的情况下,如果周围声音超过一定的分贝值,则显示提示。

目前可兼容主流PC浏览器下的所有框架,electron框架,为更好接入TRTC Web Demo 采用jquery库开发(主要是交互与样式),稍微改动即可适配原生与其他框架。

这份代码直接在TRTC的Demo中写一份js文件引入即可,即插即用。如果有需要自定义的部分,我也在代码里写了注释。

使用方法:

image.png

注:该功能需要在服务端才可使用,调试请使用Live Server。

效果展示:

image.png

源代码

说起来很简单,实际做起来有些难度,根本上是需要用js去加工音频流才行,用到了一个很旧的接口audioContext以及createMediaStreamSource相关的方法才得以实现,这里直接贴结果代码。

  • var audioContext = null;
  • var meter = null;
  • // 如果你需要对声音大小可视化,可以将canvasContext解除注释,并在html里写一个canvas元素供给后续获取
  • // var canvasContext = null;
  • var mediaStreamSource = null;
  • var WIDTH = 500;
  • var HEIGHT = 50;
  • var rafID = null;
  • var hintlock = false;
  • // jquery,如果需兼容其他框架,可以改用原生
  • $('body').append(` <div id="michint" class="alert alert-warning">
  •   <strong>注意!</strong>检测到您说话但没有打开麦克风!。
  • </div>`)
  • $('#michint').slideUp(0)
  • getStream();
  • function getStream() {
  • // 获取canvas,若想要可视化音量,在html中增加一个高度50的,宽度500的canvas,id为meter即可,也可自行修改参数
  • // canvasContext = document.getElementById("meter").getContext("2d");
  • window.AudioContext = window.AudioContext || window.webkitAudioContext;
  • try {
  • navigator.getUserMedia =
  • navigator.getUserMedia ||
  • navigator.webkitGetUserMedia ||
  • navigator.mozGetUserMedia;
  • navigator.getUserMedia(
  • {
  • "audio": {
  • "mandatory": {
  • "googEchoCancellation": "false",
  • "googAutoGainControl": "false",
  • "googNoiseSuppression": "false",
  • "googHighpassFilter": "false"
  • },
  • "optional": []
  • },
  • }, gotStream, didntGetStream);
  • } catch (e) {
  • alert('getUserMedia threw exception :' + e);
  • }
  • }
  • function didntGetStream() {
  • alert('Stream generation failed.');
  • }
  • function gotStream(stream) {
  • audioContext = new AudioContext();
  • meter = createAudioMeter(audioContext);
  • mediaStreamSource = audioContext.createMediaStreamSource(stream);
  • mediaStreamSource.connect(meter);
  • drawLoop();
  • }
  • function drawLoop() {
  • // isMicOn是demo的判定本地流是否关闭了麦克风,如有其他状态变量判断,可以自行添加。音量大小判定边界也可自行修改
  • // jquery提供的动效,以及防抖限制的时间都可自行修改。
  • if (hintlock == false && meter.volume > 0.05 && isMicOn === false) {
  • hintlock = true;
  • $('#michint').slideDown(600);
  • setTimeout(function () {
  • $("#michint").slideUp(600)
  • hintlock = false;
  • }, 3000)
  • }
  • if (typeof (canvasContext) != "undefined") {
  • canvasContext.clearRect(0, 0, WIDTH, HEIGHT);
  • canvasContext.fillStyle = "green";
  • canvasContext.fillRect(0, 0, meter.volume * WIDTH * 1.4, HEIGHT);
  • }
  • rafID = window.requestAnimationFrame(drawLoop);
  • }
  • function createAudioMeter(audioContext, clipLevel, averaging, clipLag) {
  • var processor = audioContext.createScriptProcessor(512);
  • processor.onaudioprocess = volumeAudioProcess;
  • /*用法:
  • audioNode = createAudioMeter (audioContext clipLevel,averaging,clipLag);你正在使用的audioContext。
  • clipLevel:你会考虑“剪切”的级别(0到1)。默认为0.98。
  • averaging:你希望仪表随着时间的推移变得多“平滑”。应该在0和小于1之间。默认为0.95。
  • clipLag:你希望“剪辑”指示器显示多长时间,剪切发生后,以毫秒为单位。默认为750 ms。
  • 通过node.checkClipping()访问剪辑;使用节点。关闭以接触。
  • */
  • processor.clipping = false;
  • processor.lastClip = 0;
  • processor.volume = 0;
  • processor.clipLevel = clipLevel || 0.98;
  • processor.averaging = averaging || 0.95;
  • processor.clipLag = clipLag || 750;
  • processor.connect(audioContext.destination);
  • //防抖,控制采样速度,否则音量前后会叠加。
  • processor.checkClipping =
  • function () {
  • if (!this.clipping)
  • return false;
  • if ((this.lastClip + this.clipLag) < window.performance.now())
  • this.clipping = false;
  • return this.clipping;
  • };
  • //关闭时
  • processor.shutdown =
  • function () {
  • this.disconnect();
  • this.onaudioprocess = null;
  • };
  • return processor;
  • }
  • function volumeAudioProcess(event) {
  • //音量计算
  • var buf = event.inputBuffer.getChannelData(0);
  • var bufLength = buf.length;
  • var sum = 0;
  • var x;
  • for (var i = 0; i < bufLength; i++) {
  • x = buf[i];
  • if (Math.abs(x) >= this.clipLevel) {
  • this.clipping = true;
  • this.lastClip = window.performance.now();
  • }
  • sum += x * x;
  • }
  • //对样本做一个均方根:把平方加起来……
  • var rms = Math.sqrt(sum / bufLength);
  • //现在用对前一个样本施加的平均因子来平滑这一点——这里取最大值,因为我们想要“快速增加,缓慢下降”,这样的取值更符合显示,因为现实存在回声,音量不会下降的很快。
  • this.volume = Math.max(rms, this.volume * this.averaging);
  • }
展开

核心代码讲解:

  • audioContext:这是一个用于接收音频上下文的对象,是 AudioContext 的实例,这个接口在很早以前是配合audio 标签一起使用的,可以在js层面操作audio的各种功能。但后来 audio 标签逐渐废弃,但该接口依然保留,可以获取到音频实例。包括音乐文件与麦克风,不过麦克风需要做额外处理。
  • navigator.getUserMedia:熟悉 webRTC 的开发者都知道这个用于获取设备的流,chrome的话需要先赐予权限才可获取。成功执行 gotStream并获得媒体流,失败执行 didntGetStream
    image.png
  • gotStream 分几步
    1. 使用 audioContext 中自带的 createMediaStreamSource 方法,将从getMic中得到的上下文和getUserMedia拿到的流,再创建节点,
    2. 执行 createAudioMeter,用音频上下文audioContext,创建 meter(计量表)对象
    3. 将 mediaStreamSource 连接至 meter 中
    audioContext;
    4. 用volumeAudioProcess函数,来处理缓冲区内的音频数据即可得到音量值
    }
  • createAudioMeter
  • //该段代码用于创建audio的缓冲区
  • function createAudioMeter(audioContext, clipLevel, averaging, clipLag) {
  • var processor = audioContext.createScriptProcessor(512);
  • processor.onaudioprocess = volumeAudioProcess;
  • /*用法:
  • audioNode = createAudioMeter (audioContext clipLevel,averaging,clipLag);你正在使用的audioContext。
  • clipLevel:你会考虑“剪切”的级别(0到1)。默认为0.98。
  • averaging:你希望仪表随着时间的推移变得多“平滑”。应该在0和小于1之间。默认为0.95。
  • clipLag:你希望“剪辑”指示器显示多长时间,剪切发生后,以毫秒为单位。默认为750 ms。
  • 通过node.checkClipping()访问剪辑;使用节点。关闭以接触。
  • */
  • processor.clipping = false;
  • processor.lastClip = 0;
  • processor.volume = 0;
  • processor.clipLevel = clipLevel || 0.98;
  • processor.averaging = averaging || 0.95;
  • processor.clipLag = clipLag || 750;
  • processor.connect(audioContext.destination);
  • //防抖,控制采样速度,否则音量前后会叠加。
  • processor.checkClipping =
  • function () {
  • if (!this.clipping)
  • return false;
  • if ((this.lastClip + this.clipLag) < window.performance.now())
  • this.clipping = false;
  • return this.clipping;
  • };
  • //关闭时
  • processor.shutdown =
  • function () {
  • this.disconnect();
  • this.onaudioprocess = null;
  • };
  • return processor;
  • }
展开
  • volumeAudioProcess
  • function volumeAudioProcess(event) {
  • //音量计算
  • var buf = event.inputBuffer.getChannelData(0);
  • var bufLength = buf.length;
  • var sum = 0;
  • var x;
  • for (var i = 0; i < bufLength; i++) {
  • x = buf[i];
  • if (Math.abs(x) >= this.clipLevel) {
  • this.clipping = true;
  • this.lastClip = window.performance.now();
  • }
  • sum += x * x;
  • }
  • //对样本做一个均方根:把平方加起来……
  • var rms = Math.sqrt(sum / bufLength);
  • //现在用对前一个样本施加的平均因子来平滑这一点——这里取最大值,因为我们想要“快速增加,缓慢下降”,这样的取值更符合显示,因为现实存在回声,音量不会下降的很快。
  • this.volume = Math.max(rms, this.volume * this.averaging);
  • }
本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>