Я работаю над личным проектом, который включает в себя получение аудио с YouTube, манипулирование аудио и потоковую передачу результата в браузер. Пока у меня есть первый и последний шаг вниз, но середина - это вызов.
Благодаря пакету youtube-audio-stream
получить аудио стало легко. Я хотел манипулировать необработанными аудиосэмплами, поэтому я последовал их примеру README и передал поток в декодер из пакета lame
.
Я собрал пару потоковых преобразований ... одно, чтобы объединить входящие чанки вместе, пока не будет достигнут порог размера, а другое - что-то действительно сделать с этими чанками. В конце конвейера я добавил WAV Writer (который добавляет WAV-заголовок, чтобы браузер не запутался в поступлении необработанных данных).
Это фактически приводит к нормальному выводу звука, если мое преобразование звука просто проходит по частям без каких-либо изменений. Так что я знаю, что сам трубопровод не сломан. Но по какой-то причине выполнение следующей операции приводит к искаженному шуму:
chunk.reverse();
(Это не конечная цель - это связано с БПФ - но я подумал, что реверсирование аудио-блоков было хорошей операцией для начала.)
Я ожидал, что это превратит поток в обращенные фрагменты звука, но вместо этого он исказил его до неузнаваемости. Я знаю, что Node.js-буферы - это Uint8Arrays, поэтому мне интересно, хранится ли каждый семпл как 4 отдельных 8-битных целых числа. Но я попытался сделать что-то вроде этого:
const arr = Float32Array.from(chunk);
this.push(new Buffer(arr.reverse()));
и он все еще искажен. Я также попытался написать цикл, который использовал бы Buffer.readFloatLE
и Buffer.writeFloatLE
, но он также не вел себя как ожидалось. Что мне здесь не хватает? Как я могу получить и установить данные аудиосэмпла в буфере Node.js.?
Редактировать: Добавление примера кода (я запускаю это локально как микросервис с использованием micro
):
index.js
const stream = require('youtube-audio-stream');
const wav = require('wav');
const decoder = require('lame').Decoder;
const { Chunker, AudioThing } = require('./transforms');
module.exports = (req, res) => {
const url = 'https://www.youtube.com/watch?v=-L7IdUqaZxo';
res.setHeader('Content-Type', 'audio/wav');
return stream(url)
.pipe(decoder())
.pipe(new Chunker(2 ** 16))
.pipe(new AudioThing())
.pipe(new wav.Writer());
}
transforms.js
const { Transform } = require('stream');
class Chunker extends Transform {
constructor(threshold) {
super();
this.size = 0;
this.chunks = [];
this.threshold = threshold;
}
_transform(chunk, encoding, done) {
this.size += chunk.length;
this.chunks.push(chunk);
if (this.size >= this.threshold) {
this.push(Buffer.concat(this.chunks, this.size));
this.chunks = [];
this.size = 0;
}
done();
}
}
class AudioThing extends Transform {
_transform(chunk, encoding, done) {
this.push(chunk.reverse());
done();
}
}
module.exports = { Chunker, AudioThing };
Редактировать 2: Решено! Для дальнейшего использования, вот функции утилит, которые я написал для декодирования / кодирования аудиоданных:
function decodeBuffer (buffer) {
return Array.from(
{ length: buffer.length / 2 },
(v, i) => buffer.readInt16LE(i * 2) / (2 ** 15)
);
}
function encodeArray (array) {
const buf = Buffer.alloc(array.length * 2);
for (let i = 0; i < array.length; i++) {
buf.writeInt16LE(array[i] * (2 ** 15), i * 2);
}
return buf;
}