简体   繁体   English

Node.js挂在多个setTimeout循环调用上

[英]Node.js hangs on multiple setTimeout looping calls

I'm trying to write class that will allow to me to pause the playing of an audio file. 我正在尝试编写允许我暂停播放音频文件的类。 The class accepts raw PCM data, and you provide to the class how often to send sample chunks. 该类接受原始的PCM数据,并且您向该类提供发送样本块的频率。 For example, you can specify every 20 millisecond to pass a chunk through. 例如,您可以指定每20毫秒传递一个块。 The class also implements a pause() and resume() function. 该类还实现了pause()和resume()函数。

The class also uses a SerialQueue module which I wrote to make sure that data isn't be sliced and concatenated at the same time on the buffer. 该类还使用我编写的SerialQueue模块,以确保不会同时在缓冲区上对数据进行切片和连接。 So you'll see a lot of references to that in the following code. 因此,在下面的代码中您将看到很多对此的引用。

The issue that I'm having is that it will call setTimeout a bunch of times, but then finally freeze randomly on a setTimeout. 我遇到的问题是它将多次调用setTimeout,但最终在setTimeout上随机冻结。 My processor usage will spike immediately and nothing else happens. 我的处理器使用率将立即上升,并且没有任何其他反应。

Here is the entire code plus my test code: 这是完整的代码以及我的测试代码:

var nopus = require( './lib/node-opus' );
var Speaker = require( 'speaker' );
var fs = require( 'fs' );
var path = require( 'path' );
var SerialQueue = require( './lib/serial-queue' );
var Transform = require( 'stream' ).Transform;
var inherits = require( 'util' ).inherits;

function ThrottlePCM( opts ) {
    // Default to an empty object
    if( !opts )
        opts = {};

    // Pass through the options
    Transform.call( this, opts );

    this.milliseconds = opts.milliseconds | 20;
    this.bitDepth = opts.bitDepth | 16;
    this.channels = opts.channels | 1;
    this.sampleRate = opts.sampleRate | 48000;

    // Set our frame size
    if( this.milliseconds==2.5 )
        this.frameSize = this.sampleRate/400;
    else if( this.milliseconds==5 )
        this.frameSize = this.sampleRate/200;
    else if( this.milliseconds==10 )
        this.frameSize = this.sampleRate/100;
    else if( this.milliseconds==20 )
        this.frameSize = this.sampleRate/50;
    else if( this.milliseconds==40 )
        this.frameSize = this.sampleRate/25;
    else if( this.milliseconds==60 )
        this.frameSize = 3*this.sampleRate/50;
    else
        throw new Error( "Millisecond value is not supported." );

    this.bytesPerBeat = this.frameSize*this.bitDepth/8*this.channels;

    console.log( "Bytes per beat %d.", this.bytesPerBeat );

    this.buffer = null;
    this.queue = new SerialQueue();

    // Taken from TooTallNate
    this.totalBytes = 0;
    this.startTime = Date.now();
    this.pauseTime = null;

    // Can we pass
    this.canPass = true;
    this.paused = false;
    this.flushCallback = null;
}

inherits( ThrottlePCM, Transform );

ThrottlePCM.prototype._transform = function( data, encoding, done ) {
    var that = this;

    this.queue.queue( function() {
        // Append the buffer
        if( that.buffer )
            that.buffer = Buffer.concat( [ that.buffer, data ] );
        else
            that.buffer = data;

        // Nen no tame
        if( that.canPass )
            that.passThrough();
    } );

    // We are ready for more data
    done();
};

ThrottlePCM.prototype.pause = function() {
    this.paused = true;
    this.pauseTime = Date.now();
};

ThrottlePCM.prototype.resume = function() {
    this.paused = false;
    this.startTime+= Date.now()-this.pauseTime;

    console.log( "Difference is %d: %d", Date.now()-this.pauseTime, this.startTime );

    var that = this;

    this.queue.queue( function() {
        that.passThrough();
    } );

};

ThrottlePCM.prototype.passThrough = function() {
    // Are we paused?
    if( this.paused ) {
        this.canPass = true;
        return;
    }

    // No pass now
    this.canPass = false;

    // The rest of us
    var that = this;
    var totalBeats = (Date.now()-this.startTime)/this.milliseconds;
    var expected = totalBeats*this.bytesPerBeat;

    function passMe() {
        console.log( "== Inkasemeen" );
        that.queue.queue( function() {
            if( !that.buffer ) {
                // Should we just flush?
                if( that.flushCallback ) {
                    var callback = that.flushCallback;
                    that.flushCallback = null;

                    console.log( "Antipass" );

                    callback();
                }
                else
                    that.canPass = true; // We can pass now from on timer
                return;
            }

            var output;

            if( that.buffer.length>that.bytesPerBeat ) {
                output = that.buffer.slice( 0, that.bytesPerBeat );
                that.buffer = that.buffer.slice( that.bytesPerBeat );
            }
            else {
                output = that.buffer;
                that.buffer = null;
            }

            that.push( output );
            that.totalBytes+= output.length;

            // Re-call us
            that.passThrough();
        } );
    }

    console.log( "--\nTotal Beats: %d\nTotal Bytes: %d\nExpected: %d\nBytes Per Beat: %d\nMilliseconds: %s", totalBeats, this.totalBytes, expected, this.bytesPerBeat, this.milliseconds );

    if( this.totalBytes>expected ) {
        var remainder = this.totalBytes-expected;
        var sleepTime = remainder/this.bytesPerBeat*this.milliseconds;

        console.log( "++\nSleep time: %d", sleepTime );

        if( sleepTime ) {
            setTimeout( passMe, sleepTime );
        }
        else {
            passMe();
        }
    }
    else {
        console.log( "Bytes are higher by %d (%d-%d)", expected-this.totalBytes, expected, this.totalBytes );
        passMe();
    }
};

ThrottlePCM.prototype._flush = function( done ) {
    console.log( "Flush called." );

    // No action here I don't think
    this.flushCallback = done;

    var that = this;

    this.queue.queue( function() {
        // Show ourselves flushy
        if( that.canPass )
            that.passThrough();
    } );
};

var format = {
    channels: 1,
    bitDepth: 16,
    sampleRate: 48000,
    bitrate: 16000,
    milliseconds: 60
};

var rate = nopus.getFrameSizeFromMilliseconds*format.channels*nopus.binding.sizeof_opus_int16;
var speaker = new Speaker( format );
var decoder = new nopus.Decoder( format );
var throttle = decoder.pipe( new ThrottlePCM( format ) );

throttle.pipe( speaker );

var file = fs.createReadStream( path.join( __dirname, 'files/audio/233' ) );

file.pipe( decoder );

This produces the following output: 这将产生以下输出:

Bytes per beat 5760.
--
Total Beats: 0.1
Total Bytes: 0
Expected: 576
Bytes Per Beat: 5760
Milliseconds: 60
Bytes are higher by 576 (576-0)
== Inkasemeen
--
Total Beats: 0.15
Total Bytes: 1920
Expected: 864
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 11
== Inkasemeen
--
Total Beats: 0.26666666666666666
Total Bytes: 7680
Expected: 1536
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 64
== Inkasemeen
--
Total Beats: 1.3666666666666667
Total Bytes: 13440
Expected: 7872
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 58
== Inkasemeen
--
Total Beats: 2.3833333333333333
Total Bytes: 19200
Expected: 13728
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 57
== Inkasemeen
--
Total Beats: 3.283333333333333
Total Bytes: 24960
Expected: 18912
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 63
== Inkasemeen
--
Total Beats: 4.35
Total Bytes: 30720
Expected: 25055.999999999996
Bytes Per Beat: 5760
Milliseconds: 60
++
Sleep time: 59.000000000000036

It hangs at different points throughout. 它始终挂在不同的位置。 As you can see it stops RIGHT before the passMe function is called and "== Inkasemeen" is printed. 如您所见,它会在调用passMe函数并打印“ == Inkasemeen”之前停止在右侧。

My Node.js version is v0.10.30. 我的Node.js版本是v0.10.30。

As always, thanks so much! 一如既往,非常感谢!

Found the issue! 找到了问题! It turns out that Node.js doesn't like you passing a decimal to setTimeout! 事实证明,Node.js不喜欢您将小数传递给setTimeout! Rounding off the decimal fixed the issue. 四舍五入解决了该问题。

if( sleepTime>0 ) {
    setTimeout( passMe, sleepTime|0 );
}
else {
    passMe();
}

Let me know if this code is useful to anybody. 让我知道这段代码是否对任何人有用。 If it is I can post a github when it's all finished. 如果是这样,我可以在完成后发布一个github。 It works completely, now, though. 现在,它可以完全正常工作。

Also note that there is a Throttle module out already from TooTallNate that probably satisfies most people's needs for throttling streams https://github.com/TooTallNate/node-throttle . 还请注意,TooTallNate已经提供了一个Throttle模块,该模块可能满足大多数人对流进行节流的需求https://github.com/TooTallNate/node-throttle

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM