2015-05-19 38 views
1

我想寫的類,將允許我暫停播放音頻文件。該類接受原始PCM數據,並且您向該類提供發送樣本塊的頻率。例如,您可以指定每20毫秒傳遞一個塊。該類還實現了一個暫停()和resume()函數。Node.js掛在多個setTimeout循環調用

該類還使用了我編寫的SerialQueue模塊,以確保數據不會在緩衝區上同時切片和連接。所以你會在下面的代碼中看到很多引用。

我遇到的問題是它會多次調用setTimeout,但最終會在setTimeout上隨機凍結。我的處理器使用率會立即上升,而沒有其他事情發生。

這裏是整個代碼再加上我的測試代碼:

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

這將產生以下的輸出:

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 

它掛在各個不同的點。正如你所看到的,它在調用passMe函數並且打印「== Inkasemeen」之前停止了RIGHT。

我的Node.js版本是v0.10.30。

一如既往,非常感謝!

回答

0

發現問題!事實證明,Node.js不喜歡你傳遞一個小數到setTimeout!將小數點四捨五入解決了問題。

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

讓我知道這段代碼對任何人都有用。如果是這樣,我可以在完成後發佈github。儘管如此,它現在完全可以工作。

另請注意,TooTallNate中已有一個Throttle模塊,可能滿足大多數人對節流流的需求https://github.com/TooTallNate/node-throttle