【问题标题】:How to capture the first 10 seconds of an mp3 being streamed over HTTP如何捕获通过 HTTP 流式传输的 mp3 的前 10 秒
【发布时间】:2015-05-15 02:28:21
【问题描述】:

免责声明:nodeJS 和音频解析的新手

我正在尝试在 node-icecast 的帮助下通过 expressJS 应用程序代理数字广播流,效果很好。我正在获取收音机的 mp3 流,并通过 node-lame 将 mp3 解码为 PCM,然后将其发送到扬声器。所有这些都直接来自 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 ;-) 我倒退了。现在我只想在流仍然为扬声器提供信息时切掉部分流。

【问题讨论】:

  • 基本上,而不是将其通过管道传输到 lame.Decoder() 中,您应该将它的 n 秒或字节转储到内存或文件中。一旦你有了它,你可能可以直接把它交给你提到的那个 API。
  • 谢谢。我喜欢这样的声音,虽然我不确定我会如何在 n 秒/字节(read newbie)内将其转储出来,也许你可以提供一个例子?作为答案?我想这就是我要找的。​​span>

标签: node.js audio pcm icecast node-lame


【解决方案1】:

这并不容易.. 但这个周末我已经做到了。如果你们能指出如何改进这段代码,我会很高兴。我真的不喜欢模拟流的“结束”的方法。是否有类似“分离”或“重新布线”节点中流的管道连接的部分?

首先,您应该创建自己的 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*/)
});

【讨论】:

  • 谢谢!我今晚要试试这个,然后回来报告。另外,非常感谢您的评论:)
  • 如果您不时收到“结束后写入”错误,根本原因可能是:res.emit('end');在我质疑自己是否有更好的解决方案后,我偶然发现:bennadel.com/blog/… 最后发现:nodejs.org/api/stream.html#stream_readable_unpipe_destination 如果您找到基于 unpipe() 在 10 秒后分离管道的稳定解决方案,如果您愿意,我将不胜感激提交它:)
  • 这很有效,但 'end' 事件并不总是触发(因此并不总是写入 fooBar.mp3 文件)。我改变了这个以使用你提到的 unpipe 方法,然后我实际上手动调用了 end 事件。由于这是我们自己的类,它的行为与其他流结束事件不同。这是我所拥有的:var cut = new SliceStream(lameEncoderConfig); //here's the stream to cut decodedStream.pipe(cut); setTimeout(function() { cut.end(); decodedStream.unpipe(cut); }, 10 * 1000 /*milliseconds*/);
  • 将代码放入 cmets 很麻烦,这是您的代码部分,其中包含 unpipe 和手动调用结束(不一定是流结束事件):pastebin.com/aJ5iLHSk
【解决方案2】:

我可以建议在 10 秒后使用 removeListener 吗?这将阻止未来的事件通过侦听器发送。

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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-19
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多