网络音频 API - 播放同步声音
Web Audio API - Playing synchronized sounds
我正在尝试找出通过网络音频 API 播放同步音轨的最佳方式。我想要实现的是一次播放多个 .wav 文件,同时尽可能减少音轨同步延迟。
我发现同时播放多个音轨的唯一方法是创建多个音轨并在 for 循环中循环播放它们。问题在于循环之间存在微小的延迟。延迟通常只有几毫秒,具体取决于用户机器,但是当我有 30 个音轨需要同时开始时,我的循环必须循环超过 30 个音轨并在每个音轨上调用 source.start()
他们,当循环开始第 30 首曲目时有明显的延迟。
因为我需要尽可能按时播放曲目,所以我想知道是否有其他解决方案。例如,您可以通过 Web Audio API 加载多个源,然后有一个本地全局事件可以同时启动所有这些音轨。
下面是一些显示问题的代码:
const audioBuffer1 = '...'; // Some decoded audio buffer
const audioBuffer2 = '...'; // some other decoded audio buffer
const audioBuffer3 = '...'; // and another audio buffer
const arrayOfAudioBuffers = [audioBuffer1, audioBuffer2, audioBuffer3];
const context = new AudioContext();
function play(audioBuffer) {
const source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.start();
}
for (let i = 0; i < arrayOfAudioBuffers.length; i++) {
// every time this loops the play function is
// called around 2 milliseconds after the previous
// one causing sounds to get slightly out of sync
play(arrayOfAudioBuffers[i]);
}
使用多个音轨源并设法保持良好同步的应用示例是 Splice Beatmaker。我探索了一些库,例如 Howler 和 Tone,但我相信它们似乎使用了循环方法。
很想听听有关如何解决此问题的任何建议
您可以尝试应用偏移量:
function play(audioBuffer, startTime) {
const source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.start(startTime);
}
const startTime = context.currentTime + 1.0; // one second in the future
for (let i = 0; i < arrayOfAudioBuffers.length; i++) {
play(arrayOfAudioBuffers[i], startTime);
}
此代码会将所有声音排队,以便在未来一秒同时播放。如果可行,您可以调低延迟以使声音播放更迅速,甚至可以根据音轨数量计算正确的延迟(例如,每音轨 2 毫秒 * 30 音轨 = 60 毫秒延迟)
由于浏览器可以防止指纹识别和计时攻击,现代浏览器可以降低或舍入引擎盖下的计时精度。
这意味着 source.start(offset)
在您的情况下永远不可能 100% 准确或可靠。
我推荐的是将源文件逐字节混音,然后回放最终混音。
假设所有音频源应同时启动,并且负载时间灵活,以下将起作用:
示例:
const audioBuffer1 = '...'; // Some decoded audio buffer
const audioBuffer2 = '...'; // some other decoded audio buffer
const audioBuffer3 = '...'; // and another audio buffer
const arrayOfAudioBuffers = [audioBuffer1, audioBuffer2, audioBuffer3];
我们需要通过获取最大长度的缓冲区来计算整首歌曲的长度。
let songLength = 0;
for(let track of arrayOfAudioBuffers){
if(track.length > songLength){
songLength = track.length;
}
}
接下来我创建了一个方法,它将接受 arrayOfAudioBuffers
并输出一个
最终混音。
function mixDown(bufferList, totalLength, numberOfChannels = 2){
//create a buffer using the totalLength and sampleRate of the first buffer node
let finalMix = context.createBuffer(numberOfChannels, totalLength, bufferList[0].sampleRate);
//first loop for buffer list
for(let i = 0; i < bufferList.length; i++){
// second loop for each channel ie. left and right
for(let channel = 0; channel < numberOfChannels; channel++){
//here we get a reference to the final mix buffer data
let buffer = finalMix.getChannelData(channel);
//last is loop for updating/summing the track buffer with the final mix buffer
for(let j = 0; j < bufferList[i].length; j++){
buffer[j] += bufferList[i].getChannelData(channel)[j];
}
}
}
return finalMix;
}
仅供参考:您始终可以通过对每个通道的更新进行硬编码来删除一个循环。
现在我们可以像这样使用我们的 mixDown
函数:
const mix = context.createBufferSource();
//call our function here
mix.buffer = mixDown(arrayOfAudioBuffers, songLength, 2);
mix.connect(context.destination);
//will playback the entire mixdown
mix.start()
有关网络音频精确计时的更多信息here
注意:
我们可以使用 OfflineAudioContext
来完成同样的事情,但不能保证精度并且
仍然依赖于在每个单独的源上循环和调用 start()
。
希望这对您有所帮助。
您似乎希望运行所有这些过程都像并行模式一样,以便实现零延迟。
问题是 javascript 与 java 不同,它有一个只能管理单个线程的事件循环(尽管它有更新的功能,旨在 运行 promises和类似异步的函数)。
- 我做了一些小调查,发现了这个:
Parallel.js
使用 javascript.
轻松进行多核处理
正如他们所说:
Parallel.js solves that problem by giving you high level access to multi-core processing using web workers. It runs on node and in your browser.
通常情况下,网络浏览器在每个打开的选项卡中只使用一个网络工作者,但有一些库扩展了此功能,因此您可以 运行 多个线程和 运行 并行处理您的网络应用程序。
我从未尝试过,但这里有一些文章供您开始研究:
- Mozilla.org -
平行之路 JavaScript
- Itnext.io - Parallel programming in JavaScript using Web Workers
- JonResig.com - Computing with JavaScript Web Workers
我是一个非常新手的程序员,也许这个答案不是很好,但我希望它能有所帮助。
对不起我的英语:)
你永远不会只是 source.start();
(开始意味着现在)而是你 source.start(sometimeinthefuture);
网络音频有自己的内部时钟并且非常精确。并确保您在该循环中拥有的所有曲目都具有相同的 sometimeinthefuture.
此外,您不需要像答案所示那样混合最终混音,您只需要创建一个调度系统。您通过 javascript 的超时或设置间隔在网络音频时钟上安排到未来的位置。
这在文章 tale of two clocks. There's also a simpler article that explains this. Also check out Looper.js 中进行了解释,该文章使用此调度系统概念来同步音频循环器(其中需要同步多个轨道)。
我正在尝试找出通过网络音频 API 播放同步音轨的最佳方式。我想要实现的是一次播放多个 .wav 文件,同时尽可能减少音轨同步延迟。
我发现同时播放多个音轨的唯一方法是创建多个音轨并在 for 循环中循环播放它们。问题在于循环之间存在微小的延迟。延迟通常只有几毫秒,具体取决于用户机器,但是当我有 30 个音轨需要同时开始时,我的循环必须循环超过 30 个音轨并在每个音轨上调用 source.start()
他们,当循环开始第 30 首曲目时有明显的延迟。
因为我需要尽可能按时播放曲目,所以我想知道是否有其他解决方案。例如,您可以通过 Web Audio API 加载多个源,然后有一个本地全局事件可以同时启动所有这些音轨。
下面是一些显示问题的代码:
const audioBuffer1 = '...'; // Some decoded audio buffer
const audioBuffer2 = '...'; // some other decoded audio buffer
const audioBuffer3 = '...'; // and another audio buffer
const arrayOfAudioBuffers = [audioBuffer1, audioBuffer2, audioBuffer3];
const context = new AudioContext();
function play(audioBuffer) {
const source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.start();
}
for (let i = 0; i < arrayOfAudioBuffers.length; i++) {
// every time this loops the play function is
// called around 2 milliseconds after the previous
// one causing sounds to get slightly out of sync
play(arrayOfAudioBuffers[i]);
}
使用多个音轨源并设法保持良好同步的应用示例是 Splice Beatmaker。我探索了一些库,例如 Howler 和 Tone,但我相信它们似乎使用了循环方法。
很想听听有关如何解决此问题的任何建议
您可以尝试应用偏移量:
function play(audioBuffer, startTime) {
const source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.start(startTime);
}
const startTime = context.currentTime + 1.0; // one second in the future
for (let i = 0; i < arrayOfAudioBuffers.length; i++) {
play(arrayOfAudioBuffers[i], startTime);
}
此代码会将所有声音排队,以便在未来一秒同时播放。如果可行,您可以调低延迟以使声音播放更迅速,甚至可以根据音轨数量计算正确的延迟(例如,每音轨 2 毫秒 * 30 音轨 = 60 毫秒延迟)
由于浏览器可以防止指纹识别和计时攻击,现代浏览器可以降低或舍入引擎盖下的计时精度。
这意味着 source.start(offset)
在您的情况下永远不可能 100% 准确或可靠。
我推荐的是将源文件逐字节混音,然后回放最终混音。
假设所有音频源应同时启动,并且负载时间灵活,以下将起作用:
示例:
const audioBuffer1 = '...'; // Some decoded audio buffer
const audioBuffer2 = '...'; // some other decoded audio buffer
const audioBuffer3 = '...'; // and another audio buffer
const arrayOfAudioBuffers = [audioBuffer1, audioBuffer2, audioBuffer3];
我们需要通过获取最大长度的缓冲区来计算整首歌曲的长度。
let songLength = 0;
for(let track of arrayOfAudioBuffers){
if(track.length > songLength){
songLength = track.length;
}
}
接下来我创建了一个方法,它将接受 arrayOfAudioBuffers
并输出一个
最终混音。
function mixDown(bufferList, totalLength, numberOfChannels = 2){
//create a buffer using the totalLength and sampleRate of the first buffer node
let finalMix = context.createBuffer(numberOfChannels, totalLength, bufferList[0].sampleRate);
//first loop for buffer list
for(let i = 0; i < bufferList.length; i++){
// second loop for each channel ie. left and right
for(let channel = 0; channel < numberOfChannels; channel++){
//here we get a reference to the final mix buffer data
let buffer = finalMix.getChannelData(channel);
//last is loop for updating/summing the track buffer with the final mix buffer
for(let j = 0; j < bufferList[i].length; j++){
buffer[j] += bufferList[i].getChannelData(channel)[j];
}
}
}
return finalMix;
}
仅供参考:您始终可以通过对每个通道的更新进行硬编码来删除一个循环。
现在我们可以像这样使用我们的 mixDown
函数:
const mix = context.createBufferSource();
//call our function here
mix.buffer = mixDown(arrayOfAudioBuffers, songLength, 2);
mix.connect(context.destination);
//will playback the entire mixdown
mix.start()
有关网络音频精确计时的更多信息here
注意:
我们可以使用 OfflineAudioContext
来完成同样的事情,但不能保证精度并且
仍然依赖于在每个单独的源上循环和调用 start()
。
希望这对您有所帮助。
您似乎希望运行所有这些过程都像并行模式一样,以便实现零延迟。
问题是 javascript 与 java 不同,它有一个只能管理单个线程的事件循环(尽管它有更新的功能,旨在 运行 promises和类似异步的函数)。
- 我做了一些小调查,发现了这个: Parallel.js 使用 javascript. 轻松进行多核处理
正如他们所说:
Parallel.js solves that problem by giving you high level access to multi-core processing using web workers. It runs on node and in your browser.
通常情况下,网络浏览器在每个打开的选项卡中只使用一个网络工作者,但有一些库扩展了此功能,因此您可以 运行 多个线程和 运行 并行处理您的网络应用程序。 我从未尝试过,但这里有一些文章供您开始研究:
- Mozilla.org - 平行之路 JavaScript
- Itnext.io - Parallel programming in JavaScript using Web Workers
- JonResig.com - Computing with JavaScript Web Workers
我是一个非常新手的程序员,也许这个答案不是很好,但我希望它能有所帮助。 对不起我的英语:)
你永远不会只是 source.start();
(开始意味着现在)而是你 source.start(sometimeinthefuture);
网络音频有自己的内部时钟并且非常精确。并确保您在该循环中拥有的所有曲目都具有相同的 sometimeinthefuture.
此外,您不需要像答案所示那样混合最终混音,您只需要创建一个调度系统。您通过 javascript 的超时或设置间隔在网络音频时钟上安排到未来的位置。
这在文章 tale of two clocks. There's also a simpler article that explains this. Also check out Looper.js 中进行了解释,该文章使用此调度系统概念来同步音频循环器(其中需要同步多个轨道)。