Encoder.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. var util = require( 'util' );
  2. var Transform = require( 'stream' ).Transform;
  3. var ogg_packet = require( 'ogg-packet' );
  4. var OpusEncoder = require( './OpusEncoder' );
  5. // These are the valid rates for libopus according to
  6. // https://www.opus-codec.org/docs/opus_api-1.1.2/group__opus__encoder.html#gaa89264fd93c9da70362a0c9b96b9ca88
  7. var VALID_RATES = [ 8000, 12000, 16000, 24000, 48000 ];
  8. var Encoder = function( rate, channels, frameSize ) {
  9. Transform.call( this, { readableObjectMode: true } );
  10. this.rate = rate || 48000;
  11. // Ensure the range is valid.
  12. if( VALID_RATES.indexOf( this.rate ) === -1 ) {
  13. throw new RangeError(
  14. 'Encoder rate (' + this.rate + ') is not valid. ' +
  15. 'Valid rates are: ' + VALID_RATES.join( ', ' ) );
  16. }
  17. this.channels = channels || 1;
  18. this.frameSize = frameSize || this.rate * 0.04;
  19. this.encoder = new OpusEncoder( this.rate, this.channels );
  20. this.frameOverflow = new Buffer(0);
  21. this.headerWritten = false;
  22. this.pos = 0;
  23. this.granulepos = 0;
  24. this.samplesWritten = 0;
  25. };
  26. util.inherits( Encoder, Transform );
  27. /**
  28. * Transform stream callback
  29. */
  30. Encoder.prototype._transform = function( buf, encoding, done ) {
  31. // Transform the buffer
  32. this._processOutput( buf );
  33. done();
  34. };
  35. Encoder.prototype._writeHeader = function() {
  36. // OpusHead packet
  37. var magicSignature = new Buffer( 'OpusHead', 'ascii' );
  38. var data = new Buffer([
  39. 0x01, // version
  40. this.channels,
  41. 0x00, 0x0f, // Preskip (default and recommended 3840)
  42. ( ( this.rate & 0x000000ff ) >> 0 ),
  43. ( ( this.rate & 0x0000ff00 ) >> 8 ),
  44. ( ( this.rate & 0x00ff0000 ) >> 16 ),
  45. ( ( this.rate & 0xff000000 ) >> 24 ),
  46. 0x00, 0x00, // gain
  47. 0x00, // Channel mappign (RTP, mono/stereo)
  48. ]);
  49. var header = Buffer.concat([ magicSignature, data ]);
  50. var packet = new ogg_packet();
  51. packet.packet = header;
  52. packet.bytes = header.length;
  53. packet.b_o_s = 1;
  54. packet.e_o_s = 0;
  55. packet.granulepos = -1;
  56. packet.packetno = this.pos++;
  57. this.push( packet );
  58. // OpusTags packet
  59. magicSignature = new Buffer( 'OpusTags', 'ascii' );
  60. var vendor = new Buffer( 'node-opus', 'ascii' );
  61. var vendorLength = new Buffer( 4 );
  62. vendorLength.writeUInt32LE( vendor.length, 0 );
  63. var commentLength = new Buffer( 4 );
  64. commentLength.writeUInt32LE( 0, 0 );
  65. header = new Buffer.concat([
  66. magicSignature, vendorLength, vendor, commentLength, new Buffer([ 0xff ])
  67. ]);
  68. packet = new ogg_packet();
  69. packet.packet = header;
  70. packet.bytes = header.length;
  71. packet.b_o_s = 0;
  72. packet.e_o_s = 0;
  73. packet.granulepos = -1;
  74. packet.packetno = this.pos++;
  75. packet.flush = true;
  76. this.push( packet );
  77. this.headerWritten = true;
  78. };
  79. Encoder.prototype._processOutput = function( buf ) {
  80. // Calculate the total data available and data required for each frame.
  81. var totalData = buf.length + this.frameOverflow.length;
  82. var requiredData = this.frameSize * 2 * this.channels;
  83. // Process output while we got enough for a frame.
  84. while( totalData >= requiredData ) {
  85. // If we got overflow, use it up first.
  86. var buffer;
  87. if( this.frameOverflow ) {
  88. buffer = Buffer.concat([
  89. this.frameOverflow,
  90. buf.slice( 0, requiredData - this.frameOverflow.length )
  91. ]);
  92. // Cut the already used part off the buf.
  93. buf = buf.slice( requiredData - this.frameOverflow.length );
  94. // Remove overflow. We'll set it later so it'll never be null
  95. // outside of this function.
  96. this.frameOverflow = null;
  97. } else {
  98. // We got no overflow.
  99. // Just cut the required bits from the buffer
  100. buffer = buf.slice( 0, requiredData );
  101. buf = buf.slice( requiredData );
  102. }
  103. // Flush frame and remove bits from the total data counter before
  104. // repeating loop.
  105. this._flushFrame( buffer );
  106. totalData -= requiredData;
  107. }
  108. // Store the remainign buffer in the overflow.
  109. this.frameOverflow = buf;
  110. };
  111. Encoder.prototype._flushFrame = function( frame, end ) {
  112. var encoded = this.encoder.encode( frame );
  113. this._pushEncodedBuffer(encoded, end);
  114. };
  115. Encoder.prototype._pushEncodedBuffer = function( encoded, end ) {
  116. // Write the header if it hasn't been written yet
  117. if( !this.headerWritten ) {
  118. this._writeHeader();
  119. }
  120. if( this.lastPacket ) {
  121. this.push( this.lastPacket );
  122. }
  123. // Scale the frame size into 48 kHz bitrate, which is used for the
  124. // granule positioning. We'll still update the samplesWritten just to
  125. // ensure backwards compatibility.
  126. this.granulepos += this.frameSize / this.rate * 48000;
  127. this.samplesWritten += this.frameSize;
  128. var packet = new ogg_packet();
  129. packet.packet = encoded;
  130. packet.bytes = encoded.length,
  131. packet.b_o_s = 0;
  132. packet.e_o_s = 0;
  133. packet.granulepos = this.granulepos;
  134. packet.packetno = this.pos++;
  135. packet.flush = true;
  136. this.lastPacket = packet;
  137. };
  138. Encoder.prototype._flush = function( done ) {
  139. if( this.lastPacket ) {
  140. this.lastPacket.e_o_s = 1;
  141. this.push( this.lastPacket );
  142. }
  143. done();
  144. };
  145. module.exports = Encoder;