空间音频的实战

空间音频的实战

Tags
WebMedia
WebAudio
Published
Sep 1, 2023
在现代 Web 应用中,音频处理和播放已经成为一个重要的组成部分。Web Audio API 提供了强大的工具,可以让开发者在浏览器中创建复杂的音频效果和处理。本文将介绍 Web Audio API 中的 PannerNode,并展示如何使用它来实现空间音频效果。简单实现了下面的 demo:

Demo

https://spatial-umber.vercel.app/home
戴上耳机体验更好
notion image

什么是 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: 锥体外增益,控制锥体外的声音强度。
notion image
notion image
notion image
https://www.youtube.com/watch?v=U-VisNVbXH4

实现空间音频效果

下面是一个使用 PannerNode 实现空间音频效果的代码示例。

代码详细

  1. 初始化音频
    1. 创建 AudioContext 和 PannerNode
    2. 设置 PannerNode 的属性,如 panningModel 和 distanceModel
    3. 将 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);
notion image
  1. 更新位置
    1. 使用 panner.setPosition 方法更新音频源的位置。
    2. notion image
panner.setPosition(newX, newY, newZ);

// 还可以在代码中设置 panner.orientationX、panner.orientationY 和 panner.orientationZ 属性,以确保音频源的朝向符合需求。
  1. 更新当前听者信息(当前没有动态更新)
notion image
// 更新当前人 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;
  1. 控制按钮
    1. 提供按钮来控制音频源在 X、Y 和 Z 轴上的移动。
    2. 提供按钮来启动和停止音频源的旋转。
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);
};
  1. 旋转逻辑
    1. 使用 requestAnimationFrame 实现音频源的旋转。
    2. 旋转半径保持一致的时候,根据 cos 和 sin 计算点在平面的位置信息。
    3. notion image
 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]);

其他应用场景

notion image
元宇宙场景 / 类吃鸡游戏,各种用户世界坐标更新的时候,可以根据原理,来更新接收 audio 对应的位置信息,听到比较现实的效果。
maxDistance 在最大接收距离内的就可以听到声音,然后在人移动的时候更新音频位置和方向。