WebAudio - 无缝播放音频块序列
WebAudio - seamlessly playing sequence of audio chunks
我有一个实时的、恒定的波形数据源,每秒以恒定的采样率为我提供一秒钟的单通道音频。目前我是这样玩的:
// data : Float32Array, context: AudioContext
function audioChunkReceived (context, data, sample_rate) {
var audioBuffer = context.createBuffer(2, data.length, sample_rate);
audioBuffer.getChannelData(0).set(data);
var source = context.createBufferSource(); // creates a sound source
source.buffer = audioBuffer;
source.connect(context.destination);
source.start(0);
}
音频播放正常,但在连续播放的块之间有明显的停顿(符合预期)。我想摆脱它们,我知道我必须引入某种缓冲。
问题:
- 有没有可以为我做这件事的 JS 库? (我正在搜索它们)
- 如果没有图书馆可以做到这一点,我自己应该怎么做?
- 检测一个源中的播放何时结束并让另一个源准备好在之后立即播放? (使用 AudioBufferSourceNode.onended 事件处理程序)
- 创建一个大缓冲区并一个接一个地复制我的音频块并使用 AudioBufferSourceNode.start AudioBufferSourceNode.stop 函数控制流量?
- 有什么不同吗?
您没有展示如何 audioChunkReceived
,但要获得无缝播放,您必须确保在播放之前和 之前拥有数据上一个停止播放。
完成后,您可以通过调用 start(t) 安排最新的块在前一个块结束时开始播放,其中 t 是前一个块的结束时间。
但是,如果缓冲区采样率与 context.sampleRate 不同,则可能无法流畅播放,因为将缓冲区转换为上下文速率需要重新采样。
我已经用 TypeScript 编写了一个小的 class,暂时用作缓冲区。它定义了 bufferSize 来控制它可以容纳多少块。它简短且自我描述,所以我将其粘贴在这里。还有很多需要改进的地方,所以欢迎任何想法。
(您可以使用以下方法快速将其转换为 JS:https://www.typescriptlang.org/play/ )
class SoundBuffer {
private chunks : Array<AudioBufferSourceNode> = [];
private isPlaying: boolean = false;
private startTime: number = 0;
private lastChunkOffset: number = 0;
constructor(public ctx:AudioContext, public sampleRate:number,public bufferSize:number = 6, private debug = true) { }
private createChunk(chunk:Float32Array) {
var audioBuffer = this.ctx.createBuffer(2, chunk.length, this.sampleRate);
audioBuffer.getChannelData(0).set(chunk);
var source = this.ctx.createBufferSource();
source.buffer = audioBuffer;
source.connect(this.ctx.destination);
source.onended = (e:Event) => {
this.chunks.splice(this.chunks.indexOf(source),1);
if (this.chunks.length == 0) {
this.isPlaying = false;
this.startTime = 0;
this.lastChunkOffset = 0;
}
};
return source;
}
private log(data:string) {
if (this.debug) {
console.log(new Date().toUTCString() + " : " + data);
}
}
public addChunk(data: Float32Array) {
if (this.isPlaying && (this.chunks.length > this.bufferSize)) {
this.log("chunk discarded");
return; // throw away
} else if (this.isPlaying && (this.chunks.length <= this.bufferSize)) { // schedule & add right now
this.log("chunk accepted");
let chunk = this.createChunk(data);
chunk.start(this.startTime + this.lastChunkOffset);
this.lastChunkOffset += chunk.buffer.duration;
this.chunks.push(chunk);
} else if ((this.chunks.length < (this.bufferSize / 2)) && !this.isPlaying) { // add & don't schedule
this.log("chunk queued");
let chunk = this.createChunk(data);
this.chunks.push(chunk);
} else { // add & schedule entire buffer
this.log("queued chunks scheduled");
this.isPlaying = true;
let chunk = this.createChunk(data);
this.chunks.push(chunk);
this.startTime = this.ctx.currentTime;
this.lastChunkOffset = 0;
for (let i = 0;i<this.chunks.length;i++) {
let chunk = this.chunks[i];
chunk.start(this.startTime + this.lastChunkOffset);
this.lastChunkOffset += chunk.buffer.duration;
}
}
}
}
我认为这是因为您为 2 通道分配了缓冲区。
将其更改为一个。
context.createBuffer(2, data.length, sample_rate);
到
context.createBuffer(1, data.length, sample_rate);
我有一个实时的、恒定的波形数据源,每秒以恒定的采样率为我提供一秒钟的单通道音频。目前我是这样玩的:
// data : Float32Array, context: AudioContext
function audioChunkReceived (context, data, sample_rate) {
var audioBuffer = context.createBuffer(2, data.length, sample_rate);
audioBuffer.getChannelData(0).set(data);
var source = context.createBufferSource(); // creates a sound source
source.buffer = audioBuffer;
source.connect(context.destination);
source.start(0);
}
音频播放正常,但在连续播放的块之间有明显的停顿(符合预期)。我想摆脱它们,我知道我必须引入某种缓冲。
问题:
- 有没有可以为我做这件事的 JS 库? (我正在搜索它们)
- 如果没有图书馆可以做到这一点,我自己应该怎么做?
- 检测一个源中的播放何时结束并让另一个源准备好在之后立即播放? (使用 AudioBufferSourceNode.onended 事件处理程序)
- 创建一个大缓冲区并一个接一个地复制我的音频块并使用 AudioBufferSourceNode.start AudioBufferSourceNode.stop 函数控制流量?
- 有什么不同吗?
您没有展示如何 audioChunkReceived
,但要获得无缝播放,您必须确保在播放之前和 之前拥有数据上一个停止播放。
完成后,您可以通过调用 start(t) 安排最新的块在前一个块结束时开始播放,其中 t 是前一个块的结束时间。
但是,如果缓冲区采样率与 context.sampleRate 不同,则可能无法流畅播放,因为将缓冲区转换为上下文速率需要重新采样。
我已经用 TypeScript 编写了一个小的 class,暂时用作缓冲区。它定义了 bufferSize 来控制它可以容纳多少块。它简短且自我描述,所以我将其粘贴在这里。还有很多需要改进的地方,所以欢迎任何想法。
(您可以使用以下方法快速将其转换为 JS:https://www.typescriptlang.org/play/ )
class SoundBuffer {
private chunks : Array<AudioBufferSourceNode> = [];
private isPlaying: boolean = false;
private startTime: number = 0;
private lastChunkOffset: number = 0;
constructor(public ctx:AudioContext, public sampleRate:number,public bufferSize:number = 6, private debug = true) { }
private createChunk(chunk:Float32Array) {
var audioBuffer = this.ctx.createBuffer(2, chunk.length, this.sampleRate);
audioBuffer.getChannelData(0).set(chunk);
var source = this.ctx.createBufferSource();
source.buffer = audioBuffer;
source.connect(this.ctx.destination);
source.onended = (e:Event) => {
this.chunks.splice(this.chunks.indexOf(source),1);
if (this.chunks.length == 0) {
this.isPlaying = false;
this.startTime = 0;
this.lastChunkOffset = 0;
}
};
return source;
}
private log(data:string) {
if (this.debug) {
console.log(new Date().toUTCString() + " : " + data);
}
}
public addChunk(data: Float32Array) {
if (this.isPlaying && (this.chunks.length > this.bufferSize)) {
this.log("chunk discarded");
return; // throw away
} else if (this.isPlaying && (this.chunks.length <= this.bufferSize)) { // schedule & add right now
this.log("chunk accepted");
let chunk = this.createChunk(data);
chunk.start(this.startTime + this.lastChunkOffset);
this.lastChunkOffset += chunk.buffer.duration;
this.chunks.push(chunk);
} else if ((this.chunks.length < (this.bufferSize / 2)) && !this.isPlaying) { // add & don't schedule
this.log("chunk queued");
let chunk = this.createChunk(data);
this.chunks.push(chunk);
} else { // add & schedule entire buffer
this.log("queued chunks scheduled");
this.isPlaying = true;
let chunk = this.createChunk(data);
this.chunks.push(chunk);
this.startTime = this.ctx.currentTime;
this.lastChunkOffset = 0;
for (let i = 0;i<this.chunks.length;i++) {
let chunk = this.chunks[i];
chunk.start(this.startTime + this.lastChunkOffset);
this.lastChunkOffset += chunk.buffer.duration;
}
}
}
}
我认为这是因为您为 2 通道分配了缓冲区。 将其更改为一个。
context.createBuffer(2, data.length, sample_rate);
到
context.createBuffer(1, data.length, sample_rate);