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