我想混合实时PCM音频数据,以便可以随时创建新的音频流,而可以随时关闭现有的音频流。没有流同步,它们只是在任何时候开始并在任何时候结束。我的输出应该是一个PCM编码的音频文件,该文件将所有这些流与样本之间的正确静音程度混合在一起。
免责声明:我是Node.js中流编程的新手,对如何使用音频流中的PCM数据只有基本的了解。如果我的整个基础完全错误,请纠正我。
我目前的幼稚实现的完整源代码已提供on GitHub,但我也在尝试在这里概述我认为与问题相关的部分。
假定单个可读流会生成输出PCM流。每当将此流通过管道传递到任何尝试从中读取n
字节的字节数时,它都会向其所有输入(每个PCM流各自可写的流)询问n
个字节的PCM数据。然后,输入将返回其缓冲音频数据的n
字节,或者如果缓冲音频数据不足则返回混合的缓冲音频数据和一些静默信号。
问题是我使用节点speaker包对此进行了测试,该包允许我直接通过管道传输到扬声器。 My Readable的_read
方法接收所请求的字节数。我的扬声器(或其驱动程序?)仅请求所需数量的数据,因为它们不缓冲任何内容。因此,请求的数据量与该采样率输入的数据量完全匹配。
但是,当我尝试将数据保存到文件中(在mp3编码后)时,文件写入流以比扬声器更大的频率和更多的请求数据来调用_read
。由于我用静默方式填充了多余的数据,因此产生的文件大小可以在相当长的时间内完全被静默地写入。实际上,尽管我可以略读一遍,但我什么也听不到。
export default class Input extends Writable {
readSamples (size, time) {
this.lastRead = time
// If our buffer is smaller than what's requested, fill it up with silence
if (this.buffer.length < size) {
let drainedBuffer = Buffer.concat([this.buffer, this.silence(size - this.buffer.length)])
this.buffer = this.buffer.slice(this.buffer.length)
return drainedBuffer
}
// Unshift the first _size_ elements from the buffer
let buffer = this.buffer.slice(0, size)
this.buffer = this.buffer.slice(size)
return buffer
}
_write (chunk, encoding, next) {
// Calculate how many samples we should be receiving by now
let timeDifference = process.hrtime(this.lastRead)
let timeDifferenceInNs = timeDifference[0] * NS_PER_SEC + timeDifference[1]
const channels = 2
const samplingRate = 44100
let samplesInChunk = chunk.length / channels
let samplesRequired = Math.floor(timeDifferenceInNs / NS_PER_SEC * samplingRate)
if (samplesInChunk < samplesRequired) {
this.buffer = Buffer.concat([this.buffer, this.silence(samplesRequired - samplesInChunk)])
}
this.buffer = Buffer.concat([this.buffer, chunk])
next()
}
}
。
class Mixer extends Readable {
_read (size) {
if (typeof size === 'undefined') {
// Calculate the number of samples that should be requested
// if size is not specified.
let timeSinceLastRead = process.hrtime(this.lastReadTime)
let nanosecondsSinceLastRead = timeSinceLastRead[0] * NS_PER_SEC + timeSinceLastRead[1]
let samples = nanosecondsSinceLastRead / NS_PER_SEC * this.options.samplingRate
size = samples
}
this.lastReadTime = process.hrtime()
// this.inputs also includes an input that only
// emits silence. This way even when no other inputs are
// connected, there's still some silent data coming through
// for proper timing
let buffers = this.inputs.map(input => {
return input.readSamples(size, this.lastReadTime)
})
let mixedBuffer = this.mixingFunction(buffers)
this.push(mixedBuffer)
}
}
我现在的问题:
Input
类中将输入数据变为可用时才缓冲输入数据,并且仅在调用readSamples
时才返回该数据是否正确?如何确保Mixer
调用readSamples
的时间与音频源将其数据写入输入并始终正确输入的时间一致?在编写此代码的同时查看此代码,我发现还需要说明一件事:在输入中,仅在通过_write
接收数据时才需要在开始时添加静音。相对于其他输入的正确起始偏移量。如果此输入的PCM流变为静默状态,它也将流经PCM编码的静默状态,因此最终无需人为添加静默效果。