Установите правильный размер буфера для аудиопотока PCM в node.js
Я хочу микшировать живые аудиоданные PCM, где новые аудиопотоки могут быть созданы в любое время, а существующие могут закрыться в любое время. Ни один поток не синхронизируется, они просто начинаются всегда и заканчиваются всегда. Предполагается, что мой выходной файл представляет собой аудиофайл в кодировке PCM, который смешивает все эти потоки с правильным количеством тишины между сэмплами.
Отказ от ответственности: я совершенно новичок в потоковом программировании в node.js и имею только базовое понимание того, как работать с данными PCM в аудиопотоках. Пожалуйста, поправьте меня, если весь мой фундамент полностью неверен.
Полный исходный код моей текущей наивной реализации доступен на GitHub, но я также пытаюсь обрисовать в общих чертах то, что, по моему мнению, относится к моей проблеме здесь.
Предполагается, что один читаемый поток генерирует выходной поток PCM. Каждый раз, когда бы этот поток ни направлялся, он пытается прочитать n
количество байтов из него, он запрашивает все свои входные данные (отдельные записываемые потоки для каждого потока PCM) для n
байты данных PCM. Затем входы возвращают либо n
байты их буферизованных аудиоданных или смесь буферизованных аудиоданных плюс некоторое количество тишины, если им не хватает.
Проблема заключается в том, что я проверил это с пакетом динамиков узла, который позволяет мне подключаться напрямую к моим динамикам. Мои читабельные _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 тишину, поэтому нет необходимости искусственно добавлять тишину в конце.