在现代 Web 应用中,音频处理和播放已经成为一个重要的组成部分。Web Audio API 提供了强大的工具,可以让开发者在浏览器中创建复杂的音频效果和处理。本文将介绍 Web Audio API 中的
PannerNode
,并展示如何使用它来实现空间音频效果。简单实现了下面的 demo:Demo
https://spatial-umber.vercel.app/home
戴上耳机体验更好
什么是 PannerNode
PannerNode
是 Web Audio API 中的一个接口,用于实现 3D 空间中的音频定位。它可以根据音频源的位置和听者的位置,动态调整音频的左右声道和音量,从而模拟音频在空间中的移动。PannerNode 的属性
pannerNode.panningModel
: 定义声场空间化算法,可选值包括:equalpower
: 默认值,使用等功率声场,声音强度在所有方向上保持一致,左右均衡的效果。HRTF
: 使用头部相关传递函数 (HRTF) 声场,提供更逼真的空间化效果,但计算量更大。- HRTF 指的是头相关传递函数(Head-Related Transfer Function),是一种描述声音如何从一个特定位置或方向传播到听者耳朵的函数。HRTF 反映了人类头部、耳朵以及身体结构对声音的影响,包括声音的时延、频率响应和声音强度等方面。
pannerNode.distanceModel
: 定义距离衰减模型,控制声音强度随距离的变化,可选值包括:linear
: 默认值,使用线性衰减模型。inverse
: 使用反比衰减模型。exponential
: 使用指数衰减模型。
pannerNode.refDistance
: 参考距离,单位为米,用于计算距离衰减。
pannerNode.maxDistance
: 最大距离,单位为米,超过此距离的声音将不再衰减。
pannerNode.rolloffFactor
: 衰减因子,描述当源远离监听器时减小音量的速度。
pannerNode.coneInnerAngle
: 锥体内角,单位为度,定义声音完全传播的锥形区域。
pannerNode.coneOuterAngle
: 锥体外角,单位为度,定义声音完全衰减的锥形区域。
pannerNode.coneOuterGain
: 锥体外增益,控制锥体外的声音强度。
https://www.youtube.com/watch?v=U-VisNVbXH4
实现空间音频效果
下面是一个使用
PannerNode
实现空间音频效果的代码示例。代码详细
- 初始化音频:
- 创建
AudioContext
和PannerNode
。 - 设置
PannerNode
的属性,如panningModel
和distanceModel
。 - 将
PannerNode
连接到音频上下文的目标(通常是扬声器)。
const audioElement = audioRef.current;
const context = new (window.AudioContext || window.webkitAudioContext)();
const track = context.createMediaElementSource(audioElement);
const pannerNode = context.createPanner();
pannerNode.panningModel = 'HRTF';
pannerNode.distanceModel = 'inverse';
pannerNode.refDistance = 1;
pannerNode.maxDistance = 10000;
pannerNode.rolloffFactor = 1;
pannerNode.coneInnerAngle = 60;
pannerNode.coneOuterAngle = 90;
pannerNode.coneOuterGain = 0.3;
pannerNode.setPosition(position.x, position.y, position.z);
track.connect(pannerNode).connect(context.destination);
- 更新位置:
- 使用
panner.setPosition
方法更新音频源的位置。
panner.setPosition(newX, newY, newZ);
// 还可以在代码中设置 panner.orientationX、panner.orientationY 和 panner.orientationZ 属性,以确保音频源的朝向符合需求。
- 更新当前听者信息(当前没有动态更新)
// 更新当前人 listenner 的位置
const listener = audioCtx.listener;
listener.positionX = x;
listener.positionY = y;
listener.positionZ = z;
// 更新当前听者 listenner 的头(眼睛)朝向
listener.forwardX = x;
listener.forwardY = y;
listener.forwardZ = z;
// 更新当前听者 listenner 的头顶朝向
listener.upX = x;
listener.upY = y;
listener.upZ = z;
- 控制按钮:
- 提供按钮来控制音频源在 X、Y 和 Z 轴上的移动。
- 提供按钮来启动和停止音频源的旋转。
const moveLeft = () => updatePosition({...position, x: position.x - 1});
const moveRight = () => updatePosition({...position, x: position.x + 1});
const moveUp = () => updatePosition({...position, y: position.y + 1});
const moveDown = () => updatePosition({...position, y: position.y - 1});
const moveForward = () => updatePosition({...position, z: position.z + 1});
const moveBackward = () => updatePosition({...position, z: position.z - 1});
const startRotation = () => {
setIsRotating(true);
};
const stopRotation = () => {
setIsRotating(false);
};
- 旋转逻辑:
- 使用
requestAnimationFrame
实现音频源的旋转。 - 旋转半径保持一致的时候,根据 cos 和 sin 计算点在平面的位置信息。
useEffect(() => {
if (isRotating) {
let angle = 0;
const radius = 1;// Radius of the circle
const rotate = () => {
if (!isRotating) return;
if (direct === 'right') {
angle += 0.01; // 0.01×(π/180)≈0.57∘
} else {
angle -= 0.01;
}
const newX = radius * Math.cos(angle);
const newZ = radius * Math.sin(angle);
updatePosition({x: newX, y: position.y, z: newZ});
rotationRef.current = requestAnimationFrame(rotate);
};
rotate();
return () => cancelAnimationFrame(rotationRef.current);
}
}, [isRotating]);
其他应用场景
元宇宙场景 / 类吃鸡游戏,各种用户世界坐标更新的时候,可以根据原理,来更新接收 audio 对应的位置信息,听到比较现实的效果。
maxDistance 在最大接收距离内的就可以听到声音,然后在人移动的时候更新音频位置和方向。