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