到目前为止,我们仅讨论了音频的合成与处理,但这仅是 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. 使用 Abp.Zero 搭建第三方登录模块(四):微信小程序开发

    ​简短回顾一下微信小程序端的流程: 用户通过扫码进入小程序的鉴权页面,更新状态到ACCESSED已扫码 用户点击确认授权,微信通过wx.login()接口获取第三方登录的必要信息:Code登录凭证. ...

  2. Alt+Space 快速打开切换程序 - Everything - AutoHotKey

    Alt+Space 快速打开切换程序 - Everything - AutoHotKey 需求 电脑切换任务 需要用鼠标找,效率比较低,用快捷键Alt+Space 打开列表,输入指定关键字回车,切换或 ...

  3. java学习 javaz-001 Helloworld 第一个demo

    java学习 javaz-001 Helloworld 第一个demo 目录 学习目标 前期准备 java sdk 1.8环境 编辑器IDE的选择 代码开发 目录结构 创建第一个java文件 创建第2 ...

  4. 【数据结构】哈夫曼树与哈夫曼编码(Huffman Encoding)

    一.背景 编码是信息处理的基础(重新表示信息). 普通的编码是等长编码,例如7位的ASCIL编码,对出现频率不同的字符都使用相同的编码长度.但其在传输和存储等情况下编码效率不高. 可使用不等长编码,来 ...

  5. day02-SpringMVC映射请求数据

    SpringMVC映射请求数据 1.获取参数值 在开发中,如何获取到 http://xxx/url?参数名1=参数值1&参数名2=参数值2 中的参数? 之前的案例中我们知道:提交的url的参数 ...

  6. Elasticsearch - Docker安装Elasticsearch8.12.2

    前言 最近在学习 ES,所以需要在服务器上装一个单节点的 ES 服务器环境:centos 7.9 安装 下载镜像 目前最新版本是 8.12.2 docker pull docker.elastic.c ...

  7. 【Linux】Git 安装最新版

    # 移除旧版 Git yum remove git # 安装依赖包 yum install curl-devel expat-devel gettext-devel openssl-devel zli ...

  8. [Atcoder - Distinct Trio ] 动态规划

    记f[i][j] 为前i个元素能构成的j个不同元素对的个数.对于题目j<=3;这样就有转移方程. import java.io.BufferedReader; import java.io.IO ...

  9. 记录--为啥面试官总喜欢问computed是咋实现的?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 从computed的特性出发 computed最耀眼的几个特性是啥? 1. 依赖追踪 import { reactive, compute ...

  10. 性能测试系列:Oracle数据库awr报告使用与分析

    一 AWR报告生成 1.生成AWR(Automatic Workload Repository)报告:sqlplus / as sysdbaSQL>@?/rdbms/admin/awrrpt.s ...