本来想做一个网页版的调音器,在网上搜了一下,发现已经有人实现过了,但是自己对这个原理还是很感兴趣,所以参照人家的博客和源码,用svelte重新实现了,并且放在了我的网站上。
一般用的调音器都是表盘式的,工作原理就是设备听到声音,分析声音的频率,判断出音高之后在屏幕上显示出来,在这个过程中我们可能需要解决这些问题:
- 获取设备麦克风权限
- 获取声音频率
- 将声音频率换算成音高
- 将当前声音最接近的音名显示到表盘中间,并用指针显示当前声音与该音名对应的声音之间的偏差。偏低则向左指,偏低则向右指。
看下这个过程的代码实现。
获取麦克风权限
获取麦克风权限使用 navigator.mediaDevices.getUserMedia() 这个api,
它接受一个MediaStreamConstraints (en-US) 对象,指定了请求的媒体类型和相对应的参数。返回对象是一个Promise.
这里只需要处理音频,所以参数传入{audio:true}
就可以
navigator.mediaDevices
.getUserMedia({
audio: true })
.then(handleStream)
.catch(function (error) {
alert(error.name + ': ' + error.message);
});
获取到媒体流之后,要从中分析得到声音频率。
这里有一个坑,我把代码部署到服务器之后,发现无法获取到麦克风权限,控制台报错
查了一下,TypeError: Cannot read property ‘getUserMedia’ of undefined
发现必须使用HTTPS加密通信才能获取getUserMedia()
,这个问题需要注意下。
获取声音数据
用来处理音频的API是AudioContext
AudioContext接口表示由链接在一起的音频模块构建的音频处理图,每个模块由一个AudioNode表示。音频上下文控制它包含的节点的创建和音频处理或解码的执行。在做任何其他操作之前,您需要创建一个AudioContext对象,因为所有事情都是在上下文中发生的。建议创建一个AudioContext对象并复用它,而不是每次初始化一个新的AudioContext对象,并且可以对多个不同的音频源和管道同时使用一个AudioContext对象。
分析音频流需要使用 AudioContext.createAnalyser()
AudioContext的createAnalyser()方法能创建一个AnalyserNode,可以用来获取音频时间和频率数据,以及实现数据可视化。
用js处理音频流需要用到
AudioContext.createScriptProcessor()
AudioContext 接口的createScriptProcessor() 方法创建一个ScriptProcessorNode 用于通过JavaScript直接处理音频.
scriptProcessor
是一个ScriptProcessorNode
ScriptProcessorNode 接口允许使用JavaScript生成、处理、分析音频. 它是一个 AudioNode, 连接着两个缓冲区音频处理模块, 其中一个缓冲区包含输入音频数据,另外一个包含处理后的输出音频数据. 实现了 AudioProcessingEvent (en-US) 接口的一个事件,每当输入缓冲区有新的数据时,事件将被发送到该对象,并且事件将在数据填充到输出缓冲区后结束.
监听它的audioprocess
事件就可以获得输入音频数据。
音频通路需要连接最终的目标节点
AudioContext.destination
AudioContext的destination属性返回一个AudioDestinationNode表示context中所有音频(节点)的最终目标节点,一般是音频渲染设备,比如扬声器。
将以上几个节点依次连接起来
// 获取AudioContext实例
const audioContext = new window.AudioContext();
// 创建 analyser用来获取音频频率数据
const analyser = audioContext.createAnalyser();
//
const bufferSize = 4096;
const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
const handleStream = (stream) => {
// 创建MediaStreamAudioSourceNode
// 把AudioBufferSourceNode连接到analyser
audioContext.createMediaStreamSource(stream).connect(analyser);
// 将analyser连接到 scriptProcessor,用js处理
analyser.connect(scriptProcessor);
//将scriptProcessor与destination连接,这样才能形成到达扬声器的通路
scriptProcessor.connect(audioContext.destination);
scriptProcessor.addEventListener('audioprocess', function (event) {
const data = event.inputBuffer.getChannelData(0); // 获取到音频数据
})
};
计算声音频率
音高检测算法已经很成熟了,不乏论文和资料,诸如 java、c/c++ 都有现成的库可用,而 js 在这方面显然是有缺失的。因为我的目的是快速实现一个调音器,所以我是基于一个现有的 c 音频库 aubio 用 emscripten 编译成 js,以便在浏览器里运行。
这里将c语言的音频库转译成js涉及到