Playing with AudioContext (01)

Notice

This is an old article and only available in Chinese. If you need a translation, please leave a comment and I will do my best to provide it as soon as possible.

前天尝试了一波那个什么 MediaSourceExtension,结果发现那套 API 目前限制蛮大的,而且对我来说没什么帮助(audio/x-wav 完全不正常支持,audio/mpeg 也只能在 Chrome 上使用)于是只能放弃折腾了 QAQ

昨天突然想起之前写 nanoPlayer 的时候,使用了一个叫 Audio Context 的接口,nanoPlayer 用了这个 API 里的 createAnalyzer 方法,来获得音频的频率数据,进而实现了一个频谱可视化功能。之前就注意到了这个接口中有个自定义 AudioBufferSource 的方法,可以指定若干 Float32Array 并交给浏览器播放,应该是蛮有意思的。

这里就实现一个可制定频率的正弦波音频吧,如果这个实现起来没有什么难度的话,就准备试试浏览器端解码 WAV 音频。

首先是 HTML,我们要准备一个文本框来获得指定的频率,两个按钮分别控制播放的开始与停止:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!DOCTYPE html>
<html>
  <body>
    <input id="freq" value="440" />
    <button start>start</button>
    <button stop>stop</button>
    <script>
      // TODO: code here.
    </script>
  </body>
</html>

然后初始化若干变量:

1
2
3
4
5
let ctx = new AudioContext();
let frames = ctx.sampleRate;
let start = document.querySelector("button[start]");
let stop = document.querySelector("button[stop]");
let last = null;

其中 ctx.sampleRate 是 AudioContext 的采样率,这里直接将一秒钟的采样数作为帧数,产生一段持续时间为一秒的 AudioSourceBuffer 就足够了,还有 last 是用来保存上一次的 AudioSource,可以随时调用 stop 方法来终止播放。

然后就是为 start 按钮增加一个点击事件:根据文本框里的数值产生一段指定频率的音频采样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
start.onclick = (e) => {
  if (last) last.stop();

let freq = document.querySelector('#freq').value;
// 初始化一个单声道,采样率和 AudioCotnext 一致,持续时间为 1 sec 的 AudioBuffer
let audioBuffer = ctx.createBuffer(1, ctx.sampleRate, ctx.sampleRate);
// 获得其中第一个声道的数据源,类型是 Float32Array
let nowBuffering = audioBuffer.getChannelData(0);
// 填充数据,取值范围是 [-1, 1],直接用正弦函数就行了
for (let i = 0; i < frames; ++i) {
nowBuffering[i] = Math.sin((Math.PI _ 2 _ freq) \* (i / ctx.sampleRate));
}
// 初始化一个 AudioBufferSource,并将上面的 AudioBuffer 与之绑定,并输出到 AudioContext
let source = ctx.createBufferSource();
source.loop = true;
source.buffer = audioBuffer;
source.connect(ctx.destination);
// 开始回放
source.start();
// 保存以备后续使用
last = source;
}

当然还需要对停止按钮绑定一个事件,来停止回放:

1
2
3
4
stop.onclick = (e) => {
  if (last) last.stop();
  last = null;
};

以上就是所有代码啦,效果的话,我在这里直接放个 demo 就行:

comments powered by Disqus
Except where otherwise noted, content on this blog is licensed under CC-BY 2.0.
Built with Hugo
Theme Stack designed by Jimmy