Home | History | Annotate | Download | only in js
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 /**
      8  * Function to convert an array of bytes to a base64 string
      9  * TODO(rkc): Change this to use a Uint8array instead of a string.
     10  * @param {string} bytes String containing the bytes we want to convert.
     11  * @return {string} String containing the base64 representation.
     12  */
     13 function bytesToBase64(bytes) {
     14   var bstr = '';
     15   for (var i = 0; i < bytes.length; ++i)
     16     bstr += String.fromCharCode(bytes[i]);
     17   return btoa(bstr);
     18 }
     19 
     20 /**
     21  * Function to convert a string to an array of bytes.
     22  * @param {string} str String to convert.
     23  * @return {Array} Array containing the string.
     24  */
     25 function stringToArray(str) {
     26   var buffer = [];
     27   for (var i = 0; i < str.length; ++i)
     28     buffer[i] = str.charCodeAt(i);
     29   return buffer;
     30 }
     31 
     32 /**
     33  * Creates a whispernet encoder.
     34  * @constructor
     35  * @param {Object} params Dictionary of parameters used to initialize the
     36  * whispernet encoder.
     37  * @param {Object} whisperNacl The NaclBridge object to use to communicate with
     38  * the whispernet wrapper.
     39  */
     40 function WhisperEncoder(params, whisperNacl) {
     41   params = params || {};
     42   this.repetitions_ = params.repetitions || 3;
     43 
     44   this.whisperNacl_ = whisperNacl;
     45   this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
     46 
     47   var msg = {
     48     type: 'initialize_encoder',
     49     sample_rate: params.sampleRate || 48000.0,
     50     upsampling_factor: params.bitsPerSample || 16,
     51   };
     52   this.whisperNacl_.send(JSON.stringify(msg));
     53 }
     54 
     55 /**
     56  * Method to encode a token.
     57  * @param {string} token Token to encode.
     58  * @param {boolean} audible Whether we should use encode audible samples.
     59  * @param {boolean} raw Whether we should return the encoded samples in raw
     60  * format or as a Wave file.
     61  */
     62 WhisperEncoder.prototype.encode = function(token, audible, raw) {
     63   var msg = {
     64     type: 'encode_token',
     65     // Trying to send the token in binary form to Nacl doesn't work correctly.
     66     // We end up with the correct string + a bunch of extra characters. This is
     67     // true of returning a binary string too; hence we communicate back and
     68     // forth by converting the bytes into an array of integers.
     69     token: stringToArray(token),
     70     repetitions: this.repetitions_,
     71     use_dtmf: audible,
     72     return_raw_samples: raw
     73   };
     74   this.whisperNacl_.send(JSON.stringify(msg));
     75 };
     76 
     77 /**
     78  * Method to set the callback for encoded audio data received from the encoder
     79  * when we finish encoding a token.
     80  * @param {function(string, ArrayBuffer)} callback Callback which will receive
     81  * the audio samples.
     82  */
     83 WhisperEncoder.prototype.setAudioDataCallback = function(callback) {
     84   this.audioDataCallback_ = callback;
     85 };
     86 
     87 /**
     88  * Method to handle messages from the whispernet NaCl wrapper.
     89  * @param {Event} e Event from the whispernet wrapper.
     90  * @private
     91  */
     92 WhisperEncoder.prototype.onNaclMessage_ = function(e) {
     93   var msg = e.data;
     94   if (msg.type == 'encode_token_response') {
     95     this.audioDataCallback_(
     96         { token: bytesToBase64(msg.token), audible: msg.audible }, msg.samples);
     97   }
     98 };
     99 
    100 /**
    101  * Creates a whispernet decoder.
    102  * @constructor
    103  * @param {Object} params Dictionary of parameters used to initialize the
    104  * whispernet decoder.
    105  * @param {Object} whisperNacl The NaclBridge object to use to communicate with
    106  * the whispernet wrapper.
    107  */
    108 function WhisperDecoder(params, whisperNacl) {
    109   params = params || {};
    110 
    111   this.whisperNacl_ = whisperNacl;
    112   this.whisperNacl_.addListener(this.onNaclMessage_.bind(this));
    113 
    114   var msg = {
    115     type: 'initialize_decoder',
    116     channels: params.channels || 1,
    117     sample_rate: params.sampleRate || 48000.0,
    118     upsampling_factor: params.bitsPerSample || 16,
    119     max_candidates: 1,
    120     max_buffer_duration_in_seconds: 3
    121   };
    122   this.whisperNacl_.send(JSON.stringify(msg));
    123 }
    124 
    125 /**
    126  * Method to request the decoder to wipe its internal buffer.
    127  */
    128 WhisperDecoder.prototype.wipeDecoder = function() {
    129   var msg = {
    130     type: 'wipe_decode_buffer'
    131   };
    132   this.whisperNacl_.send(JSON.stringify(msg));
    133 };
    134 
    135 /**
    136  * Method to request the decoder to detect a broadcast.
    137  */
    138 WhisperDecoder.prototype.detectBroadcast = function() {
    139   var msg = {
    140     type: 'detect_broadcast'
    141   };
    142   this.whisperNacl_.send(JSON.stringify(msg));
    143 };
    144 
    145 /**
    146  * Method to request the decoder to process samples.
    147  * @param {ArrayBuffer} samples Array of samples to process.
    148  */
    149 WhisperDecoder.prototype.processSamples = function(samples) {
    150   // For sample processing, the Nacl module doesn't expect any frills in the
    151   // message, just send the samples directly.
    152   this.whisperNacl_.send(samples);
    153 };
    154 
    155 /**
    156  * Method to set the callback for decoded tokens received from the decoder.
    157  * @param {function(!Array.string)} callback Callback to receive the list of
    158  * decoded tokens.
    159  */
    160 WhisperDecoder.prototype.setReceiveCallback = function(callback) {
    161   this.tokenCallback_ = callback;
    162 };
    163 
    164 /**
    165  * Method to set the callback for receiving the detect callback status received
    166  * from the decoder.
    167  * @param {function()} callback Callback to set to receive the detect broadcast
    168  * status.
    169  */
    170 WhisperDecoder.prototype.onDetectBroadcast = function(callback) {
    171   this.detectBroadcastCallback_ = callback;
    172 };
    173 
    174 /**
    175  * Method to handle messages from the whispernet NaCl wrapper.
    176  * @param {Event} e Event from the whispernet wrapper.
    177  * @private
    178  */
    179 WhisperDecoder.prototype.onNaclMessage_ = function(e) {
    180   var msg = e.data;
    181   if (msg.type == 'decode_tokens_response') {
    182     this.handleCandidates_(JSON.parse(msg.tokens), msg.audible);
    183   } else if (msg.type == 'detect_broadcast_response') {
    184     this.detectBroadcastCallback_(msg.detected);
    185   }
    186 };
    187 
    188 /**
    189  * Method to receive tokens from the decoder and process and forward them to the
    190  * token callback registered with us.
    191  * @param {!Array.string} candidates Array of token candidates.
    192  * @param {boolean} audible Whether the received candidates are from the audible
    193  *     decoder or not.
    194  * @private
    195  */
    196 WhisperDecoder.prototype.handleCandidates_ = function(candidates, audible) {
    197   if (!this.tokenCallback_ || !candidates || candidates.length == 0)
    198     return;
    199 
    200   var returnCandidates = [];
    201   for (var i = 0; i < candidates.length; ++i) {
    202     returnCandidates[i] = { token: bytesToBase64(candidates[i]),
    203                             audible: audible };
    204   }
    205   this.tokenCallback_(returnCandidates);
    206 };
    207 
    208