4、Web Audio API 中音频缓冲区与音频源节点的使用详解

Web Audio API 中音频缓冲区与音频源节点的使用详解

在 Web 音频处理领域,Web Audio API 提供了强大的功能来处理和播放音频。本文将深入探讨音频缓冲区(AudioBuffer)和音频缓冲区源节点(AudioBufferSourceNode)的使用,包括它们的创建、操作以及播放机制。

1. 音频缓冲区(AudioBuffer)

音频缓冲区(AudioBuffer)是 Web Audio API 中用于存储音频数据的重要对象,它代表驻留在内存中的音频资产。以下是关于音频缓冲区的详细介绍:
- 存储内容 :音频缓冲区可以存储多个音频通道的数据,每个通道由一个浮点线性 PCM 值数组表示,其标称范围为 [-1, 1] 。这些数据最长可达一分钟,对于更长的音频,如音乐配乐,则应使用音频元素和 MediaElementAudioSourceNode 进行流式传输。
- 创建方式
- 使用 context.createBuffer 方法:

var audioBuffer = context.createBuffer(numberOfChannels, length, sampleRate);
其中,`context` 是现有的音频上下文,`numberOfChannels` 表示缓冲区的通道数,`length` 决定了缓冲区的样本大小,即每个通道的长度,`sampleRate` 是音频数据的采样率(以赫兹为单位)。该方法创建的音频缓冲区的音频数据将初始化为零(静音)。
- 直接构造:
var audioBuffer = new AudioBuffer({options});
这里的 `options` 包含前面提到的参数,`length` 和 `sampleRate` 必须指定,如果未指定 `numberOfChannels`,则默认为 1。
- 使用 `audioContext` 的 `decodeAudioData` 方法(后续会详细讨论)。
  • 属性 :音频缓冲区具有 duration 属性,表示音频数据的持续时间(以秒为单位),计算公式为 length / sampleRate
  • 方法
    • getChannelData(channel) :获取指定通道的内部数据,返回一个新的 Float32Array
    • copyToChannel(source, channelNumber, bufferOffset) :将源数组复制到音频缓冲区的指定通道。
    • copyFromChannel(destination, channelNumber, bufferOffset) :将音频缓冲区的指定通道复制到目标数组。

以下是一个创建音频缓冲区并填充随机值的示例:

<button id='start' onclick='context.resume()'>Make noise</button>
<script>
    let i, channel, context = new AudioContext();
    let source = context.createBufferSource();
    let nFrames = 3 * context.sampleRate;
    source.buffer = context.createBuffer(2, nFrames, context.sampleRate);
    for (channel = 0; channel < 2; channel++) {
        for (i = 0; i < nFrames; i++) {
            source.buffer.getChannelData(channel)[i] = 2 * Math.random() - 1;
        }
    }
    source.start();
    source.connect(context.destination);
</script>
2. 音频缓冲区源节点(AudioBufferSourceNode)

音频缓冲区源节点(AudioBufferSourceNode)用于从音频缓冲区播放音频,它提供了灵活且精确的播放控制。以下是其详细介绍:
- 属性
- buffer :表示要播放的音频资产。
- detune :一个以分(cents)为单位的额外参数,默认值为 0,用于调制音频流的渲染速度。
- loop :一个布尔值,指示是否应无限循环播放 loopStart loopEnd 之间的音频数据区域,默认值为 false
- loopEnd :如果 loop 属性为 true ,则表示循环结束的位置,默认值为 0,可设置为 0 到缓冲区持续时间之间的任何值。
- loopStart :如果 loop 属性为 true ,则表示循环开始的位置,默认值为 0,会被限制在 0 到缓冲区持续时间之间的值。
- playbackRate :音频流的渲染速度,默认值为 1。

playbackRate detune 形成一个复合参数,实际播放速率的计算公式为 PlaybackRate = playbackRate(t) · 2^(detune(t) / 1200) ,该播放速率直接影响产生声音的音高和持续时间。

  • 方法
    • start(when, offset, duration) :安排音频在精确的时间开始播放,该方法只能调用一次。
      • when :指定声音开始播放的时间,与 AudioContext currentTime 属性使用相同的时间坐标系。如果该值小于 currentTime ,则声音将立即开始播放。
      • offset :指定播放开始的位置,如果为 0,则从缓冲区的开头开始播放。该值不能超过音频缓冲区的持续时间,因此在调用 start() 方法之前必须加载缓冲区。
      • duration :要播放的声音的持续时间,以缓冲区总内容的秒数表示,包括任何完整或部分的循环迭代。该值的单位独立于 playbackRate 的影响。
3. 播放机制详解

音频缓冲区的播放机制较为复杂,因为多个参数相互作用决定了何时播放何种音频。以下通过具体示例来详细说明:
- 无循环播放 :播放完全由缓冲区的持续时间、计算得到的 playbackRate 以及 start 方法的参数( when offset duration )决定。
- 假设一个持续时间为 10 秒的缓冲区,计算得到的播放速率为 0.5。调用 start() 方法将以半速输出 10 秒的缓冲区内容,产生 20 秒的可听输出。在 AudioContext 时钟的时间 t (起始时间为 0),缓冲区的播放头位置将为 0.5t 秒,到达缓冲区末尾时停止播放。
- 如果使用 start(1, 3, 4) 启动缓冲区源,将在调用 start 方法 1 秒后开始播放音频,此时播放头位置为缓冲区的第 3 秒。它将以半速继续播放,直到 AudioContext 时钟为调用 start 方法后的 1 + 4 = 5 秒,此时播放头位置为 3 + 4 * 0.5 = 5 秒。
- 如果计算得到的 playbackRate 为 2,同样使用 start(1, 3, 4) 启动缓冲区源,将尝试在调用 start 方法后的 1 到 5 秒内播放声音。但最终播放头位置将为 3 + 4 * 2 = 11 秒,超过了 10 秒的缓冲区持续时间。因此,在 3.5 秒后播放头位置到达缓冲区末尾时将产生静音,因为 3 + 3.5 * 2 = 10。
- 循环播放 :循环播放的一般概念是,循环缓冲区的播放行为应与包含循环音频内容连续出现的非循环缓冲区相同,但不考虑插值效果。
- 假设一个持续时间为 10 秒的缓冲区, playbackRate 为 0.5, loop 设置为 true ,但 loopStart loopEnd 保持默认值。调用 start() 方法将以指定的速度不断循环播放整个缓冲区。
- 如果使用 start(1, 3, 4) 启动,将在调用 start 方法 1 秒后开始播放音频,播放头位置为缓冲区的第 3 秒。但由于 4 秒的持续时间结束后,播放头位置仅到达 3 + 4 * 0.5 = 5 秒,因此不会发生循环。
- 如果使用 start(1, 3, 24) 启动,将在调用 start 方法 1 秒后开始播放,播放头位置为 3 秒。在调用 start 方法后的 1 + 14 = 15 秒,播放头位置到达 3 + 14 * 0.5 = 10 秒,即缓冲区末尾,此时播放头位置将重置为 0。在调用 start 方法后的 1 + 24 秒,由于 start() 方法中指定的 24 秒持续时间已达到,将停止播放,此时播放头位置为 ((1 + 24) - (1 + 14)) * 0.5 = 5 秒。
- 考虑 loopStart loopEnd 参数 :循环体从 loopStart loopEnd 之间的区域。循环不会影响 start 方法的 offset 参数的解释,播放始终从请求的 offset 开始,只有在播放过程中遇到循环体时才开始循环。
- 假设 buffer.duration = 10 playbackRate = 0.5 loop = true loopStart = 2 loopEnd = 9 ,并使用 start(1, 3, 24) 启动节点。缓冲区将按之前的方式开始播放,但当播放头位置达到 9 秒时循环结束,这发生在调用 start 方法后的 1 + (8 - 3) / 0.5 = 13 秒。注意,由于开始延迟了 1 秒,这只是 24 秒持续时间中的 12 秒。此时播放头位置将重置为 2 秒,并开始从播放头位置 2 秒到 9 秒的循环。但在 24 秒的持续时间结束时,播放头位置仅到达 2 + 12 * 0.5 = 8 秒,因此不会完成这个循环。

4. 示例代码:创建和播放音频缓冲区

以下是一个创建音频缓冲区并加载到源节点的简单示例,用于生成立体声噪声:

<button id='start' onclick='context.resume()'>Make noise</button>
<script>
    let i, channel, context = new AudioContext();
    let source = context.createBufferSource();
    let nFrames = 3 * context.sampleRate;
    source.buffer = context.createBuffer(2, nFrames, context.sampleRate);
    for (channel = 0; channel < 2; channel++) {
        for (i = 0; i < nFrames; i++) {
            source.buffer.getChannelData(channel)[i] = 2 * Math.random() - 1;
        }
    }
    source.start();
    source.connect(context.destination);
</script>
5. 示例代码:改变音频缓冲区的播放

以下代码允许用户改变音频缓冲区的播放速率、起始偏移、持续时间以及循环参数:

<button id='start'>Play!</button><br><br>
<input type='range' min=0.25 max=4 step='any' value=1 id='rate'>
Playback rate<br>
<input type='range' min=0 max=3 step='any' value=0 id='offset'>
Offset<br>
<input type='range' min=0 max=3 step='any' value=3 id='duration'>
Duration<br><br>
<input type='checkbox' id='loop'>
Loop<br>
<input type='range' min=0 max=3 step='any' value=0 id='loopstart'>
Loop start<br>
<input type='range' min=0 max=3 step='any' value=3 id='loopend'>
Loop end<br>
<script src='Playback.js'></script>
<script>
    let context = new AudioContext();
    var source = new AudioBufferSourceNode(context);
    let bufferDuration = 3 * context.sampleRate;
    let array = new Float32Array(bufferDuration);
    let instantFreq;
    for (var i = 0; i < array.length; i++) {
        instantFreq = 1000 * i / bufferDuration;
        array[i] = Math.sin(2 * Math.PI * instantFreq * i / context.sampleRate);
    }
    start.onclick = function () {
        context.resume();
        source = new AudioBufferSourceNode(context);
        source.buffer = context.createBuffer(1, bufferDuration, context.sampleRate);
        source.buffer.copyToChannel(array, 0);
        source.playbackRate.value = rate.value;
        source.loop = loop.checked;
        source.loopStart = loopstart.value;
        source.loopEnd = loopend.value;
        source.start(0, offset.value);
        source.connect(context.destination);
    }
    rate.oninput = () => source.playbackRate.value = rate.value;
</script>
6. 总结

通过本文的介绍,我们详细了解了 Web Audio API 中音频缓冲区(AudioBuffer)和音频缓冲区源节点(AudioBufferSourceNode)的使用。音频缓冲区用于存储音频数据,而音频缓冲区源节点则提供了灵活且精确的播放控制。通过合理设置各种参数,如 playbackRate loop loopStart loopEnd ,可以实现多样化的音频播放效果。同时,通过示例代码,我们展示了如何创建、操作和播放音频缓冲区,希望这些内容能帮助你更好地利用 Web Audio API 进行音频处理。

以下是一个简单的流程图,展示了音频缓冲区和音频源节点的基本使用流程:

graph TD;
    A[创建音频上下文] --> B[创建音频缓冲区];
    B --> C[填充音频数据];
    C --> D[创建音频缓冲区源节点];
    D --> E[设置源节点属性];
    E --> F[连接到目标节点];
    F --> G[调用 start 方法播放音频];

此外,为了更清晰地对比不同播放情况下的参数和效果,我们可以列出以下表格:
| 播放类型 | 缓冲区持续时间(秒) | 播放速率 | start 参数(when, offset, duration) | 播放头位置变化 | 最终效果 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| 无循环 | 10 | 0.5 | (0, 0, 10) | 0.5t 秒 | 20 秒可听输出,到达末尾停止 |
| 无循环 | 10 | 2 | (1, 3, 4) | 开始 3 秒,结束 11 秒 | 3.5 秒后到达末尾产生静音 |
| 循环(默认端点) | 10 | 0.5 | (1, 3, 24) | 多次循环,最终 5 秒 | 24 秒后停止,播放头位置 5 秒 |
| 循环(自定义端点) | 10 | 0.5 | (1, 3, 24),loopStart = 2,loopEnd = 9 | 多次循环,最终 8 秒 | 24 秒后停止,未完成最后一次循环 |

通过以上的介绍和示例,你可以深入理解 Web Audio API 中音频缓冲区和音频源节点的使用,从而实现更加复杂和多样化的音频处理效果。

Web Audio API 中音频缓冲区与音频源节点的使用详解

7. 循环播放的进一步探讨

循环播放是 Web Audio API 中一个非常有用的功能,它可以让音频在指定的区域内不断重复播放。在上一部分,我们已经介绍了基本的循环播放机制,下面我们将进一步探讨循环播放的一些细节和注意事项。

  • 循环端点的约束 loopStart loopEnd 的值必须在 0 到缓冲区持续时间之间,并且 loopEnd 必须大于或等于 loopStart 。如果违反这些约束,循环将被视为包含整个缓冲区内容。例如,如果 loopStart = 5 loopEnd = 3 ,那么循环将包含整个缓冲区。
  • 循环相关属性的动态变化 :在缓冲区播放过程中,可以动态改变循环相关的属性,如 loop loopStart loopEnd 。例如,可以在播放过程中打开或关闭循环,或者改变循环的起始和结束位置。但需要注意的是,这些改变可能会影响播放的连续性,因为可能会导致播放头位置的突然变化。
  • 插值处理 :当循环端点不是精确的样本帧偏移,或者播放速率不等于 1 时,循环的播放需要进行插值处理,以确保循环的开始和结束能够平滑拼接。例如,在图 3.1 中,展示了不同情况下的插值处理效果。

以下是一个简单的表格,总结了循环播放的相关要点:
| 要点 | 说明 |
| ---- | ---- |
| 端点约束 | loopStart loopEnd 必须在 0 到缓冲区持续时间之间,且 loopEnd >= loopStart |
| 属性动态变化 | 可在播放过程中改变 loop loopStart loopEnd ,但可能影响播放连续性 |
| 插值处理 | 循环端点非精确样本帧偏移或播放速率不等于 1 时需要插值处理 |

8. 改变播放速率和采样率的影响

改变播放速率和采样率会对音频的播放产生重要影响,下面我们将详细讨论这些影响。

  • 播放速率的影响 playbackRate detune 共同决定了音频的实际播放速率。播放速率的变化会直接影响音频的音高和持续时间。例如,当播放速率为 2 时,音频的音高会升高一个八度,持续时间会减半;当播放速率为 0.5 时,音频的音高会降低一个八度,持续时间会加倍。
  • 采样率的影响 :如果缓冲区的采样率与音频上下文的采样率不同,需要进行重采样处理。重采样可能会导致音频质量的损失,特别是在采样率差异较大的情况下。在图 3.1 中,展示了不同采样率对播放的影响。

以下是一个流程图,展示了改变播放速率和采样率的处理流程:

graph TD;
    A[设置播放速率和采样率] --> B{是否需要重采样};
    B -- 是 --> C[进行重采样处理];
    B -- 否 --> D[正常播放];
    C --> D;
    D --> E[插值处理(如果需要)];
    E --> F[输出音频];
9. 子样本偏移和插值处理

子样本偏移和插值处理是 Web Audio API 中处理音频播放的重要技术,下面我们将详细介绍。

  • 子样本偏移 :子样本偏移指的是播放开始的位置不是精确的样本帧偏移。例如,播放可能会在半个采样周期后开始。在这种情况下,每个输出样本都需要通过插值计算得到,以确保音频的连续性。
  • 插值处理 :插值处理用于在不同样本之间进行平滑过渡,特别是在循环播放、改变播放速率或采样率、子样本偏移等情况下。常见的插值方法包括线性插值,它通过在相邻样本之间绘制直线来计算中间值。

以下是一个示例代码,展示了如何处理子样本偏移:

<script>
    let context = new AudioContext();
    let source = context.createBufferSource();
    let buffer = context.createBuffer(1, 1000, context.sampleRate);
    // 填充缓冲区数据
    for (let i = 0; i < buffer.length; i++) {
        buffer.getChannelData(0)[i] = Math.sin(2 * Math.PI * 440 * i / context.sampleRate);
    }
    source.buffer = buffer;
    // 设置子样本偏移
    let offset = 0.5 / context.sampleRate;
    source.start(0, offset);
    source.connect(context.destination);
</script>
10. 总结与实践建议

通过前面的介绍,我们对 Web Audio API 中音频缓冲区和音频源节点的使用有了深入的了解。以下是一些总结和实践建议:

  • 总结

    • 音频缓冲区(AudioBuffer)用于存储音频数据,可通过多种方式创建和填充。
    • 音频缓冲区源节点(AudioBufferSourceNode)提供了灵活且精确的播放控制,可通过设置各种属性和方法来实现多样化的播放效果。
    • 播放机制涉及多个参数的相互作用,包括 playbackRate loop loopStart loopEnd 等,需要仔细考虑以实现预期的效果。
    • 循环播放、改变播放速率和采样率、子样本偏移等情况需要进行插值处理,以确保音频的连续性和质量。
  • 实践建议

    • 在创建音频缓冲区时,根据实际需求合理设置通道数、长度和采样率。
    • 在使用音频缓冲区源节点时,仔细设置各种属性和方法,特别是 start 方法的参数,以实现精确的播放控制。
    • 在处理循环播放时,注意循环端点的约束和动态变化的影响。
    • 在改变播放速率和采样率时,考虑重采样和插值处理的影响,以确保音频质量。
    • 在处理子样本偏移时,使用插值方法来计算输出样本,以确保音频的连续性。

通过遵循这些建议,你可以更好地利用 Web Audio API 进行音频处理,实现更加复杂和多样化的音频效果。希望本文对你有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值