到目前为止,我们仅讨论了音频的合成与处理,但这仅是 Web Audio API 提供的一半功能。另一半功能则是音频的分析,它播放起来应该是什么样子的。它最典型的例子就是音频可视化,但其实有更多的其它应用场景,包括声调检测,节减检测,语音识别等,这些已大大超出本书范围。

对于游戏或交互式应用开发者来说,这是一个重要的主题,原因有几点。首先,一个好的可视化分析器可以用于类似调式工具(显然这是除了你耳朵之外,良好的计量工具)用于调音。其次,对于某些关于音乐相关的游戏或应用来说可视化是重点比如游戏“吉它英雄”或者应用软件 GarageBand (苹果电脑上吉它教学软件)

频率分析

在 Web Audio API 分析声音是最主要的方式利用 AnalyserNodes。这些节点不会对声音本身做任何改变,可以在音频上下文任意处调用。一旦在音频图中创建了这样的节点,它就会提供两种主要方式用于查看声音波形:时域和频域上

得到的结果是基于特定缓冲区大小的 FFT 分析。我们有一些定制化节点输出的属性可用:

  • fftSize

    定义缓冲区大小用于实现分析。大小一定是2的幂。较高的值将导致对信号进行更细粒度的分析,但代价是一些性能损失。

  • frequencyBinCount

    这个属性是只读的,自动为 fftSize / 2。

  • smoothingTimeConstant

    值范围是 0 - 1. 值为1会导致较大的移动平均平滑结果。值为零意味着没有移动平均线,结果波动很快。

最基本的设置就是把分析节点插到我们感兴趣的音频图谱中:

// 假设节点A与B普普通通相连
var analyser = context.createAnalyser();
A.connect(analyser);
analyser.connect(B);

然后我们就可以得到时域或频域的数组了:

var freqDomain = new Float32Array(analyser.frequencyBinCount);
analyser.getFloatFrequencyData(freqDomain);

在上面的代码例子中,freqDomain 是一个频域 32 位浮点数组。这些数组内存储的值都被标准化为 0-1。输出的指标可以在0和奈奎斯特频率之间线性映射,奈奎斯特频率被定义为采样率的一半(在 Web Audio API 中通过 context.sampleRate 获取)。下面的代码片段将 frequency 映射到频率数组中的正确位置:

奈奎斯特频率是离散信号系统采样频率的一半也就是 1/2 fs,fs为采样频率

function getFrequencyValue(frequency) {
var nyquist = context.sampleRate/2;
var index = Math.round(frequency/nyquist * freqDomain.length);
return freqDomain[index];
}

如果我们分析的是一个 1000 Hz 的正弦波,举例,我们期望调用 getFrequencyValue(1000) 时返回图像内的峰值,如图 5-1。

频域通过调用 getByteFrequencyData 使用8位无符号存储也可以。 这些值就是无符号整型,在分析节点( analyzer node)它会缩放以适配在最大分贝和最小分贝之间(即在 dBFS中,decibels full scale)。因此可以根据需要调整这些参数以缩放输出。

图 5-1,一个 1000Hz 的可视化声音(全频域是从 0 至 22050Hz)

requestAnimationFrame 实现动画

如果我们想要对我们的声音进行可视化,我们需要周期性的查询分析节点(analyzer node), 处理返回的结果,并渲染出来。我们可以利用 JavaScript 的定时器实现,setInterval, setTimeout, 但现在有更好用的:requestAnimationFrame。该 API 允许浏览器将你的自定义绘制函数合并到浏览器本地渲染循环中,这对性能来讲会有很大提升。不同于指定固定绘制间隔需要等待浏览器空闲时才来处理你的定时器不同,你只需要将它提交到队列中,浏览器会以最快的速度执行它。

由于 requestAnimationFrame 还是处于实验性质,你需要为其指定浏览器前缀,且给它定一个相似功能的 setTimeout 来兜底。代码如下:

window.requestAnimationFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();

一但定义好了 requestAnimationFrame 函数,我们需要利用它来查询分析节点得到音频流的详细信息。

requestAnimationFrame 现在早就加入肯德基豪华午餐了直接用就可以了

声音可视化

把它们全组合在一起,设置一个渲染循环用于查询和渲染之前用到的分析节点,将存进 freqDomain 数组:

var freqDomain = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(freqDomain);
for (var i = 0; i < analyser.frequencyBinCount; i++) {
var value = freqDomain[i];
var percent = value / 256;
var height = HEIGHT * percent;
var offset = HEIGHT - height - 1;
var barWidth = WIDTH/analyser.frequencyBinCount;
var hue = i/analyser.frequencyBinCount * 360;
drawContext.fillStyle = 'hsl(' + hue + ', 100%, 50%)';
drawContext.fillRect(i * barWidth, offset, barWidth, height);
}

对时域也可以进行类似的操作

var timeDomain = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(timeDomain);
for (var i = 0; i < analyser.frequencyBinCount; i++) {
var value = timeDomain[i];
var percent = value / 256;
var height = HEIGHT * percent;
var offset = HEIGHT - height - 1;
var barWidth = WIDTH/analyser.frequencyBinCount;
drawContext.fillStyle = 'black';
drawContext.fillRect(i * barWidth, offset, 1, 1);
}

此代码将时域内的值利用 HTML5 canvas 绘制,创建一个简单的可视化图形,在代表频域数据的彩色色条状图的顶部绘制了一个波形线条。

结果在 canvas 上绘制出来应该如图 5-2

图 5-2 某一时刻的可视化截图

以上分析器节点的代码实现 demo 可参考 https://github.com/willian12345/WebAudioAPI/tree/master/examples/ch05/demo.html

我们处理可视化方案遗漏了很多数据。但对音乐的可视化来说足够了。当然如果我们想综合全面分析整个音频缓冲区,我们需要看看其它的方法。

额外部分--显示整个声音文件的音量高低图

这一部分并非 Web Audio API 书说中述,是译者本人所述

这是我在项目中遇到的一个问题

网上一堆例子都是显示实时音频信号的,就像上一节中的那样

可是如果我想要的是显示整段 mp3 文件的音量高低图呢?

即如何分析整断音频的数据?

原理是加载 mp3 文件后解码分析音频数据,获取某一段音频数据的采样最高和最低点并绘制出来

首先就是从获取音频文件开始 利用 html 的 <input type="file" /> 标签获取 file 后:

const reader = new FileReader();
reader.onload = function (e) {
const audioContext = new (window.AudioContext ||
window.webkitAudioContext)();
audioContext.decodeAudioData(e.target.result, function (buffer) {
// 获取音频缓冲区数据
const channelData = buffer.getChannelData(0);
// 绘制波形
drawWaveform(channelData);
});
};
reader.readAsArrayBuffer(file);

利用 FileReader 以 ArrayBuffer 形式读取文件内容

再使用 audioContext.decodeAudioData 解码

解码后获取 channelData, 此时的 channelData 就包含该通道的音频样本数据,可以理解为标准化后的 PCM 数据

如果忘记了什么是 PCM 可以回顾第一章的内容

如图 5-5 只要解析这个 channelData 内的 PCM 数据并绘制出来就行了 drawWaveform(channelData)

function drawWaveform(data) {
const canvas = document.getElementById("waveform"); // 获取canvas元素
const ctx = canvas.getContext("2d"); // 获取2D绘图上下文
const width = canvas.width; // canvas的宽度
const height = canvas.height; // canvas的高度
const step = Math.ceil(data.length / width); // 计算每个画布像素对应的音频样本数
const amp = height / 2; // 放大因子,用于控制波形在画布上的高度 ctx.fillStyle = "#fff"; // 设置填充颜色为白色
ctx.fillRect(0, 0, width, height); // 填充整个画布为白色 ctx.beginPath(); // 开始绘制新的路径
ctx.moveTo(0, amp); // 将绘图游标移动到画布中央的起始点 // 绘制波形
for (let i = 0; i < width; i += 4) {
// 遍历画布的每一个像素
let min = 1.0; // 初始化最小值
let max = -1.0; // 初始化最大值
for (let j = 0; j < step; j++) {
// 遍历与当前像素对应的音频样本 const datum = data[i * step + j]; // 获取单个音频样本
if (datum < min) min = datum; // 更新最小值
if (datum > max) max = datum; // 更新最大值
} ctx.lineTo(i, (1 + min) * amp); // 绘制从当前位置到最小值的线 ctx.lineTo(i, (1 + max) * amp); // 绘制从当前位置到最大值的线 }
ctx.stroke(); // 根据路径绘制线条 }

图 5-5 加载 test1.mp3 后显示的图

可参考 https://github.com/willian12345/WebAudioAPI/tree/master/examples/ch05/volume-visualization1.html

步骤:

  1. 根据 canvas 的 width 确定采样数据范围()宽度

    const step = Math.ceil(data.length / width);

  2. 在 step 采样数据范围循环找出最高与最低音量

for (let j = 0; j < step; j++) {
// 遍历与当前像素对应的音频样本 const datum = data[i * step + j]; // 获取单个音频样本
if (datum < min) min = datum; // 更新最小值
if (datum > max) max = datum; // 更新最大值
}
  1. 有了音量高低的值,直接绘制线条或柱型就可以了
ctx.lineTo(i, (1 + min) * amp); // 绘制从当前位置到最小值的线
ctx.lineTo(i, (1 + max) * amp); // 绘制从当前位置到最大值的线

把线条独立开后加点色彩或许更好看

function drawWaveform(data) {
const canvas = document.getElementById("waveform"); // 获取canvas元素
const ctx = canvas.getContext("2d"); // 获取2D绘图上下文
const width = canvas.width; // canvas的宽度
const height = canvas.height; // canvas的高度
const step = Math.ceil(data.length / width); // 计算每个画布像素对应的音频样本数
const amp = height / 2; // 放大因子,用于控制波形在画布上的高度 ctx.fillStyle = "#fff"; // 设置填充颜色为白色
ctx.fillRect(0, 0, width, height); // 填充整个画布为白色 ctx.beginPath(); // 开始绘制新的路径
ctx.moveTo(0, amp); // 将绘图游标移动到画布中央的起始点 // 绘制波形
for (let i = 0; i < width; i += 4) {
// 根据 i 遍历画布的宽度
let min = 1.0; // 初始化最小值
let max = -1.0; // 初始化最大值
ctx.moveTo(i, amp);
for (let j = 0; j < step; j++) {
// 遍历与当前像素对应的音频样本 const datum = data[i * step + j]; // 获取单个音频样本
if (datum < min) min = datum; // 更新最小值
if (datum > max) max = datum; // 更新最大值
}
var hue = (i / width) * 360; ctx.beginPath()
ctx.strokeStyle = "hsl(" + hue + ", 100%, 50%)";
ctx.moveTo(i, amp);
ctx.lineTo(i, (1 + min) * amp); // 绘制从当前位置到最小值的线
ctx.stroke(); // 根据路径绘制线条 ctx.beginPath()
ctx.moveTo(i, amp);
ctx.lineTo(i, (1 + max) * amp); // 绘制从当前位置到最大值的线
ctx.stroke(); // 根据路径绘制线条
}
}

图 5-3

可参考 https://github.com/willian12345/WebAudioAPI/tree/master/examples/ch05/volume-visualization2.html

还可以把 i 的间隔缩小,再把 step 的大小也缩小试试,我得到了下面 图 5-4 效果

图 5-4

在网上见过有人用扇形或者螺旋形来可视化音频。效果也是相当的酷

请自行搜索,只要得到数据了实现起来还是比较简单的


注:转载请注明出处博客园:王二狗Sheldon池中物 (willian12345@126.com)

Web Audio API 第5章 音频的分析与可视化的更多相关文章

  1. 关于HTML5音频——audio标签和Web Audio API各平台浏览器的支持情况

    对比audio标签 和 Web Audio API 各平台浏览器的支持情况:   audio element Web Audio API desktop browsers Chrome 14 Yes  ...

  2. 【HTML5】Web Audio API打造超炫的音乐可视化效果

    HTML5真是太多炫酷的东西了,其中Web Audio API算一个,琢磨着弄了个音乐可视化的demo,先上效果图: 项目演示:别说话,点我!  源码已经挂到github上了,有兴趣的同学也可以去st ...

  3. 关于Web Audio API的入门

    Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容.它允许你开发复杂的混音,音效,平移以及更多. 可以先看一下MDN的这篇文章<Web Audio API的运用 ...

  4. 使用Web Audio API绘制音波图

    摘要:Web Audio API是对<audio> 标签功能上的补充,我们可以用它完成混音.音效.平移等各种复杂的音频处理,本文简单的使用其完成音波图的绘制. PS:本例子使用ES6编程, ...

  5. H5的Web Audio Api

    概述 研究Web Audio Api的主要原因是:工作中需要在ios中实现声音的淡出效果,主要是通过setInterval来改audio标签的volume属性实现的,但是ios上面volume属性是只 ...

  6. Web Audio API之手把手教你用web api处理声音信号:可视化音乐demo

    1.Web Audio API 介绍 Web Audio API 提供了在Web上控制音频的一个非常有效通用的系统 ,这些通用系统通俗的讲就是我们可以利用Web Audio API提供的各种方法操作各 ...

  7. HTML5 ——web audio API 音乐可视化(二)

    上一篇 web audio API 音乐可视化(一)介绍了一些基本的API,以及如何简单的播放一个音频,本篇介绍一下怎么对获取到的音频进行分析,并将分析后的数据绘制成图像. 最终效果请戳这里; 完整版 ...

  8. HTML5 ——web audio API 音乐可视化(一)

    使用Web Audio API可以对音频进行分析和操作,最终实现一个音频可视化程序. 最终效果请戳这里; 完整版代码请戳这里,如果还看得过眼,请给一个start⭐ 一.API AudioContext ...

  9. 【Web Audio API】 — 那些年的 web audio

    转 TAT.Jdo:[Web Audio API] - 那些年的 web audio 这主题主要是早期对 web audio api的一些尝试,这里整理一下以便以后翻阅,如有错误,诚请指正. 在这之前 ...

  10. [Javascript] Intro to the Web Audio API

    An introduction to the Web Audio API. In this lesson, we cover creating an audio context and an osci ...

随机推荐

  1. Go语言VSCode开发环境配置

    最近学习Golang,先把开发环境配置好. 一.安装Go语言开发包 https://golang.google.cn/dl/ 按步骤安装即可,安装完成后需要设置Windows环境变量 配置好,做个测试 ...

  2. mysql for update是锁表还是锁行

    转载至我的博客 https://www.infrastack.cn ,公众号:架构成长指南 在并发一致性控制场景中,我们常常用for update悲观锁来进行一致性的保证,但是如果不了解它的机制,就进 ...

  3. 代码片段管理软件 - 发现 utools 这个工具还行 windows软件

    代码片段管理软件 - 发现 utools 这个工具还行 windows软件 介绍 这个软件不是专业的代码片段工具 好在还能凑合用 最完美的还是苹果那个软件,但是用的win没办法了 这个可以粘贴到vsc ...

  4. 海词 dict.cn 有 词义饼状分布图 和 词性饼状分布图 - 词典推荐

    海词 dict.cn 有 词义饼状分布图 和 词性饼状分布图 http://dict.cn/like

  5. Java求解汉诺塔

    汉诺塔 汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗 ...

  6. iBatis查询API

    "一切皆Socket!" 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信 ...

  7. 26_H.264编码实战

    目录 使用FFmpeg命令进行H.264编码 使用FFmpeg代码实现H.264编码 1.获取编码器 2.创建上下文 3.打开编码器 4.创建 AVFrame 5.创建 AVPacket 6.打开文件 ...

  8. 基于Rust的Tile-Based游戏开发杂记(02)ggez绘图实操

    尽管ggez提供了很多相关特性的[demo](ggez/examples at master · ggez/ggez (github.com))供运行查看,但笔者第一次使用的时候还是有很多疑惑不解.经 ...

  9. 记录--Vue 2和Vue 3:谁将成为前端界的披萨与汉堡之争?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 在前端开发的世界里,Vue.js一直是一个备受追捧的框架.随着Vue 3的发布,开发者们开始热烈讨论它与Vue 2之间的差异和优势.就像披 ...

  10. 嵌入式C语言设计学习

    由C语言到嵌入式C语言设计 1.C语言的特性 C语言,最为基本的高级编程语言,已经有许多其他的延伸.而在嵌入式设计中,主要的拓展内容就是相关的硬件设备的驱动.这部分的设计为其提供了更加灵活的应用环境. ...