• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
13function 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 */
25function 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 */
40function 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 */
62WhisperEncoder.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 */
83WhisperEncoder.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 */
92WhisperEncoder.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 */
108function 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 */
128WhisperDecoder.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 */
138WhisperDecoder.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 */
149WhisperDecoder.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 */
160WhisperDecoder.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 */
170WhisperDecoder.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 */
179WhisperDecoder.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 */
196WhisperDecoder.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