最近几天需要实现一个需求,就是在实现两用户视频通信的基础上,进一步显示其即时的麦克风音量,以及用户可以本地测试自己的麦克风音量。需求和开黑的时候调试自己的麦克风音量类似,只不过实现的环境是浏览器环境以及语言用的是JavaScript。
这个需求让我想起了前段时间比较火的一款安卓游戏:百分音符酱,游戏核心玩法就是通过玩家的声音大小控制小人的跳高距离用于翻越障碍躲避敌人,所以通过以下的实现方式,是能够实现该游戏比较核心的一个部分:声音录入和数据输出
实现效果
麦克风音量处获取当前麦克风的输入数据,根据一定的规则转化成progressBar显示。
API使用情况
MediaDevices.getUserMedia()
AudioContext
requestAnimationFrame()
MediaDevices.getUserMedia()
MediaDevices.getUserMedia()方法提示用户允许使用一个视频和/或一个音频输入设备,例如相机或屏幕共享和/或麦克风。如果用户给予许可,就返回一个Promise 对象,MediaStream对象作为此Promise对象的Resolved[成功]状态的回调函数参数
用该接口为了获取一个音频流(mediaStream)的输入
AudioContext
AudioContext接口表示由音频模块连接而成的音频处理图,每个模块对应一个AudioNode
用于对音频流进行分析,拿到相关频域数据
requestAnimationFrame()
window.requestAnimationFrame() 方法告诉浏览器您希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前更新动画。该方法将在重绘之前调用的回调作为参数。
如果您想在下一个重绘时为另一个Frame设置动画,您的回调例程必须调用
requestAnimationFrame()
设置对音频流的平均采样时间,通过递归调用该接口实现每秒采样60次,即60帧
基本设计:
代码实现
首先的就是音频流的获取
let constraints = {
audio: true,
video: false
};
navigator.mediaDevices.getUserMedia(constraints)
.then((stream)=>{
...
})
这段代码在then
的回调里面能够拿到用户输入的mediaStream
,之后我会在stream
后接入分析器,这里设置 video
为false
的原因是因为我们不需要获得视频流,需要注意的一点是10.*
版本(及以下)的Safari
并不支持getUserMedia()
,具体的各浏览器支持情况如下:
将音频流引入AudioContext并接入分析器
....then((stream)=>{
let audioCtx = new AudioContext()
let source = audioCtx.createMediaStreamSource(stream) // 引入音频流
let analyser = audioCtx.createAnalyser() // 创建分析器
source.connect(analyser) // 将stream连接到分析器上
analyser.fftSize = 1024 // 可以理解为设置频率的单位取样宽度
let array = new Uint8Array(analyser.frequencyBinCount); // 保证长度满足
analyser.getByteFrequencyData(array) // 将当前频域数据拷贝进数组
...
})
AudioContext的设计风格类似于Pipe-And-Filter
,即管道过滤模型,通过对流进行过滤分析实现更多的功能,并且将各功能通过connect
连接起来。这里如果再次analyser.connect(audioCtx.destination)
就可以把音频流引入扬声器实现即时播放功能。analyser.fftSize
可以理解为设置频率平均采样的单位长度。如文档:
根据以上的解释,当我设置.fftSize = 32的时候 取得的数组长度为16
设置为1024的时候,数组长度为512
到目前为止,我们已经进行的一次频率的采样了,下一步就是如何根据时间进行平均采样,那么,就必须要用到requestAnimationFrame()
...
let onePick = () => {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array); // 将当前的频率数据传入array
console.log(array);
requestAnimationFrame(onePick)
} // 采样函数
requestAnimationFrame(onePick) // 开始执行采样
该onePick函数会每秒执行60次,也就会取60次的样本,然后,我们会拿到一个数组,里面包含了频域的数据,这数据拿来怎么用,就看业务是什么了
最后看下实现的效果吧(从网上扒了一个canvas绘图的代码)
网友评论