Как захватить первые 10 секунд mp3, передаваемого по HTTP

отказ от ответственности: новичок в nodeJS и разборе аудио

Я пытаюсь проксировать цифровой радиопоток через приложение ExpressJS с помощью node-icecast который отлично работает. Я получаю mp3-поток радио и через node-lame декодирую mp3 в PCM, а затем отправляю это к динамикам. Все это работает прямо из примера readme проекта github:

var lame = require('lame');
var icecast = require('icecast');
var Speaker = require('speaker');

// URL to a known Icecast stream
var url = 'http://firewall.pulsradio.com';

// connect to the remote stream
icecast.get(url, function (res) {

// log the HTTP response headers
console.error(res.headers);

// log any "metadata" events that happen
res.on('metadata', function (metadata) {
  var parsed = icecast.parse(metadata);
  console.error(parsed);
});

// Let's play the music (assuming MP3 data).
// lame decodes and Speaker sends to speakers!
res.pipe(new lame.Decoder())
   .pipe(new Speaker());
});

Сейчас я пытаюсь настроить службу для идентификации музыки с помощью Doreso API. Проблема в том, что я работаю с потоком и у меня нет файла (и я еще недостаточно знаю о потоках, доступных для чтения и записи, и медленном обучении). Я некоторое время оглядывался, пытаясь записать поток (в идеале в память), пока у меня не было около 10 секунд. Затем я передал бы эту часть звука в свой API, однако я не знаю, возможно ли это или не знаю, с чего начать с нарезки 10 секунд потока. Я подумал, что, возможно, попытаюсь передать поток в ffmpeg, поскольку у него есть опция -t для длительности, и, возможно, это может ограничить его, однако у меня это еще не работает.

Любые предложения по сокращению потока до 10 секунд были бы потрясающими. Спасибо!

Обновлено: изменился мой вопрос, так как я изначально думал, что получаю PCM и конвертирую в mp3 ;-) У меня было наоборот. Теперь я просто хочу отрезать часть потока, пока поток все еще питает динамик.


person mfink    schedule 12.03.2015    source источник
comment
По сути, вместо того, чтобы передавать его в lame.Decoder(), вы должны просто вывести n секунд или байтов либо в память, либо в файл. Как только вы это сделаете, вы, вероятно, можете просто передать его прямо в тот API, который вы упомянули.   -  person TBR    schedule 12.03.2015
comment
Спасибо за это. Мне нравится, как это звучит, хотя я не уверен, что знаю, как выгрузить это за n секунд / байтов (читать новичка). Возможно, вы могли бы привести пример? как ответ? Я думаю, это то, что я ищу.   -  person mfink    schedule 12.03.2015


Ответы (2)


Это не так просто... но я справился в эти выходные. Я был бы рад, если бы вы, ребята, могли указать, как хотя бы улучшить этот код. Мне не очень нравится подход к имитации «конца» потока. Есть ли что-то вроде «отсоединения» или «переподключения» частей пайп-проводки потоков в узле?

Во-первых, вы должны создать свой собственный класс Writable Stream, который сам создает неубедительный экземпляр кодировки. Этот доступный для записи поток будет получать декодированные данные PCM.

Это работает следующим образом:

var stream = require('stream');
var util = require('util');
var fs = require('fs');
var lame = require('lame');
var streamifier = require('streamifier');
var WritableStreamBuffer = require("stream-buffers").WritableStreamBuffer;

var SliceStream = function(lameConfig) {

    stream.Writable.call(this);

    this.encoder = new lame.Encoder(lameConfig);

    // we need a stream buffer to buffer the PCM data
    this.buffer = new WritableStreamBuffer({
        initialSize: (1000 * 1024),      // start as 1 MiB.
        incrementAmount: (150 * 1024)    // grow by 150 KiB each time buffer overflows.
    });
};

util.inherits(SliceStream, stream.Writable);

// some attributes, initialization
SliceStream.prototype.writable = true;
SliceStream.prototype.encoder = null;
SliceStream.prototype.buffer = null;

// will be called each time the decoded steam emits "data" 
// together with a bunch of binary data as Buffer
SliceStream.prototype.write = function(buf) {

    //console.log('bytes recv: ', buf.length);

    this.buffer.write(buf);

    //console.log('buffer size: ', this.buffer.size());
};

// this method will invoke when the setTimeout function
// emits the simulated "end" event. Lets encode to MP3 again...
SliceStream.prototype.end = function(buf) {

    if (arguments.length) {
        this.buffer.write(buf);
    }
    this.writable = false;

    //console.log('buffer size: ' + this.buffer.size());

    // fetch binary data from buffer
    var PCMBuffer = this.buffer.getContents();

    // create a stream out of the binary buffer data
    streamifier.createReadStream(PCMBuffer).pipe(

        // and pipe it right into the MP3 encoder...
        this.encoder
    );

    // but dont forget to pipe the encoders output 
    // into a writable file stream
    this.encoder.pipe(
        fs.createWriteStream('./fooBar.mp3')
    );
};

Теперь вы можете направить поток decoded в экземпляр вашего класса SliceStream следующим образом (в дополнение к другим каналам):

icecast.get(streamUrl, function(res) {

    var lameEncoderConfig = {
        // input
        channels: 2,        // 2 channels (left and right)
        bitDepth: 16,       // 16-bit samples
        sampleRate: 44100,   // 44,100 Hz sample rate

        // output
        bitRate: 320,
        outSampleRate: 44100,
        mode: lame.STEREO // STEREO (default), JOINTSTEREO, DUALCHANNEL or MONO
    };
    var decodedStream = res.pipe(new lame.Decoder());

    // pipe decoded PCM stream into a SliceStream instance
    decodedStream.pipe(new SliceStream(lameEncoderConfig));

    // now play it...
    decodedStream.pipe(new Speaker());

    setTimeout(function() {

        // after 10 seconds, emulate an end of the stream.
        res.emit('end');

    }, 10 * 1000 /*milliseconds*/)
});
person kyr0    schedule 24.03.2015
comment
Спасибо! Я собираюсь попробовать это сегодня вечером и отчитаться. Кроме того, огромное спасибо за ваш комментарий :) - person mfink; 24.03.2015
comment
Если вы время от времени получаете сообщение об ошибке записи после завершения, основной причиной может быть: res.emit('end'); После того, как я задался вопросом, есть ли лучшее решение, я наткнулся на: bennadel.com/blog/ И, наконец, заметил: nodejs.org/api/stream.html#stream_readable_unpipe_destination Если вы найдете стабильное решение для отключения каналов через 10 секунд на основе unpipe(), я был бы признателен, если бы вы представили его :) - person kyr0; 24.03.2015
comment
Это работает, и это здорово, но событие «конец» не всегда срабатывало (и, следовательно, не всегда записывалось в файл fooBar.mp3). Я изменил это, чтобы использовать метод unpipe, как вы упомянули, и затем я фактически вызываю конечное событие вручную. Поскольку это наш собственный класс, он не ведет себя как другие события окончания потока. Вот что у меня есть: var cut = new SliceStream(lameEncoderConfig); //here's the stream to cut decodedStream.pipe(cut); setTimeout(function() { cut.end(); decodedStream.unpipe(cut); }, 10 * 1000 /*milliseconds*/); - person mfink; 25.03.2015
comment
неудобно помещать код в комментарии, вот часть вашего кода с unpipe и ручным вызовом end (не обязательно событием конца потока): pastebin.com/aJ5iLHSk - person mfink; 25.03.2015

Могу ли я предложить использовать removeListener через 10 секунд? Это предотвратит отправку будущих событий через прослушиватель.

var request = require('request'),
    fs = require('fs'),
    masterStream = request('-- mp3 stream --')

var writeStream = fs.createWriteStream('recording.mp3'),
handler = function(bit){
   writeStream.write(bit);
}

masterStream.on('data', handler);
setTimeout(function(){
    masterStream.removeListener('data', handler);
    writeStream.end();
}, 1000 * 10);
person Adam F    schedule 25.08.2016