前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TRTC Web端 仿腾讯会议麦克风静音检测

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

原创
作者头像
楚歌
修改2021-04-27 22:52:53
2.6K0
修改2021-04-27 22:52:53
举报

项目背景

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

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

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

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

使用方法:

image.png
image.png

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

效果展示:

image.png
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
    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);
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 项目背景
  • 效果展示:
  • 源代码
  • 核心代码讲解:
相关产品与服务
实时音视频
实时音视频(Tencent RTC)基于腾讯21年来在网络与音视频技术上的深度积累,以多人音视频通话和低延时互动直播两大场景化方案,通过腾讯云服务向开发者开放,致力于帮助开发者快速搭建低成本、低延时、高品质的音视频互动解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档