• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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 #include "content/browser/speech/google_streaming_remote_engine.h"
6 
7 #include <vector>
8 
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/rand_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.h"
16 #include "content/browser/speech/audio_buffer.h"
17 #include "content/browser/speech/proto/google_streaming_api.pb.h"
18 #include "content/public/common/content_switches.h"
19 #include "content/public/common/speech_recognition_error.h"
20 #include "content/public/common/speech_recognition_result.h"
21 #include "google_apis/google_api_keys.h"
22 #include "net/base/escape.h"
23 #include "net/base/load_flags.h"
24 #include "net/url_request/url_fetcher.h"
25 #include "net/url_request/url_request_context.h"
26 #include "net/url_request/url_request_context_getter.h"
27 #include "net/url_request/url_request_status.h"
28 
29 using net::URLFetcher;
30 
31 namespace content {
32 namespace {
33 
34 const char kWebServiceBaseUrl[] =
35     "https://www.google.com/speech-api/full-duplex/v1";
36 const char kDownstreamUrl[] = "/down?";
37 const char kUpstreamUrl[] = "/up?";
38 const AudioEncoder::Codec kDefaultAudioCodec = AudioEncoder::CODEC_FLAC;
39 
40 // This matches the maximum maxAlternatives value supported by the server.
41 const uint32 kMaxMaxAlternatives = 30;
42 
43 // TODO(hans): Remove this and other logging when we don't need it anymore.
DumpResponse(const std::string & response)44 void DumpResponse(const std::string& response) {
45   DVLOG(1) << "------------";
46   proto::SpeechRecognitionEvent event;
47   if (!event.ParseFromString(response)) {
48     DVLOG(1) << "Parse failed!";
49     return;
50   }
51   if (event.has_status())
52     DVLOG(1) << "STATUS\t" << event.status();
53   for (int i = 0; i < event.result_size(); ++i) {
54     DVLOG(1) << "RESULT #" << i << ":";
55     const proto::SpeechRecognitionResult& res = event.result(i);
56     if (res.has_final())
57       DVLOG(1) << "  FINAL:\t" << res.final();
58     if (res.has_stability())
59       DVLOG(1) << "  STABILITY:\t" << res.stability();
60     for (int j = 0; j < res.alternative_size(); ++j) {
61       const proto::SpeechRecognitionAlternative& alt =
62           res.alternative(j);
63       if (alt.has_confidence())
64         DVLOG(1) << "    CONFIDENCE:\t" << alt.confidence();
65       if (alt.has_transcript())
66         DVLOG(1) << "    TRANSCRIPT:\t" << alt.transcript();
67     }
68   }
69 }
70 
GetAPIKey()71 std::string GetAPIKey() {
72   const CommandLine& command_line = *CommandLine::ForCurrentProcess();
73   if (command_line.HasSwitch(switches::kSpeechRecognitionWebserviceKey)) {
74     DVLOG(1) << "GetAPIKey() used key from command-line.";
75     return command_line.GetSwitchValueASCII(
76         switches::kSpeechRecognitionWebserviceKey);
77   }
78 
79   std::string api_key = google_apis::GetAPIKey();
80   if (api_key.empty())
81     DVLOG(1) << "GetAPIKey() returned empty string!";
82 
83   return api_key;
84 }
85 
86 }  // namespace
87 
88 const int GoogleStreamingRemoteEngine::kAudioPacketIntervalMs = 100;
89 const int GoogleStreamingRemoteEngine::kUpstreamUrlFetcherIdForTesting = 0;
90 const int GoogleStreamingRemoteEngine::kDownstreamUrlFetcherIdForTesting = 1;
91 const int GoogleStreamingRemoteEngine::kWebserviceStatusNoError = 0;
92 const int GoogleStreamingRemoteEngine::kWebserviceStatusErrorNoMatch = 5;
93 
GoogleStreamingRemoteEngine(net::URLRequestContextGetter * context)94 GoogleStreamingRemoteEngine::GoogleStreamingRemoteEngine(
95     net::URLRequestContextGetter* context)
96     : url_context_(context),
97       previous_response_length_(0),
98       got_last_definitive_result_(false),
99       is_dispatching_event_(false),
100       state_(STATE_IDLE) {}
101 
~GoogleStreamingRemoteEngine()102 GoogleStreamingRemoteEngine::~GoogleStreamingRemoteEngine() {}
103 
SetConfig(const SpeechRecognitionEngineConfig & config)104 void GoogleStreamingRemoteEngine::SetConfig(
105     const SpeechRecognitionEngineConfig& config) {
106   config_ = config;
107 }
108 
StartRecognition()109 void GoogleStreamingRemoteEngine::StartRecognition() {
110   FSMEventArgs event_args(EVENT_START_RECOGNITION);
111   DispatchEvent(event_args);
112 }
113 
EndRecognition()114 void GoogleStreamingRemoteEngine::EndRecognition() {
115   FSMEventArgs event_args(EVENT_END_RECOGNITION);
116   DispatchEvent(event_args);
117 }
118 
TakeAudioChunk(const AudioChunk & data)119 void GoogleStreamingRemoteEngine::TakeAudioChunk(const AudioChunk& data) {
120   FSMEventArgs event_args(EVENT_AUDIO_CHUNK);
121   event_args.audio_data = &data;
122   DispatchEvent(event_args);
123 }
124 
AudioChunksEnded()125 void GoogleStreamingRemoteEngine::AudioChunksEnded() {
126   FSMEventArgs event_args(EVENT_AUDIO_CHUNKS_ENDED);
127   DispatchEvent(event_args);
128 }
129 
OnURLFetchComplete(const URLFetcher * source)130 void GoogleStreamingRemoteEngine::OnURLFetchComplete(const URLFetcher* source) {
131   const bool kResponseComplete = true;
132   DispatchHTTPResponse(source, kResponseComplete);
133 }
134 
OnURLFetchDownloadProgress(const URLFetcher * source,int64 current,int64 total)135 void GoogleStreamingRemoteEngine::OnURLFetchDownloadProgress(
136     const URLFetcher* source, int64 current, int64 total) {
137   const bool kPartialResponse = false;
138   DispatchHTTPResponse(source, kPartialResponse);
139 }
140 
DispatchHTTPResponse(const URLFetcher * source,bool end_of_response)141 void GoogleStreamingRemoteEngine::DispatchHTTPResponse(const URLFetcher* source,
142                                                        bool end_of_response) {
143   DCHECK(CalledOnValidThread());
144   DCHECK(source);
145   const bool response_is_good = source->GetStatus().is_success() &&
146                                 source->GetResponseCode() == 200;
147   std::string response;
148   if (response_is_good)
149     source->GetResponseAsString(&response);
150   const size_t current_response_length = response.size();
151 
152   DVLOG(1) << (source == downstream_fetcher_.get() ? "Downstream" : "Upstream")
153            << "HTTP, code: " << source->GetResponseCode()
154            << "      length: " << current_response_length
155            << "      eor: " << end_of_response;
156 
157   // URLFetcher provides always the entire response buffer, but we are only
158   // interested in the fresh data introduced by the last chunk. Therefore, we
159   // drop the previous content we have already processed.
160   if (current_response_length != 0) {
161     DCHECK_GE(current_response_length, previous_response_length_);
162     response.erase(0, previous_response_length_);
163     previous_response_length_ = current_response_length;
164   }
165 
166   if (!response_is_good && source == downstream_fetcher_.get()) {
167     DVLOG(1) << "Downstream error " << source->GetResponseCode();
168     FSMEventArgs event_args(EVENT_DOWNSTREAM_ERROR);
169     DispatchEvent(event_args);
170     return;
171   }
172   if (!response_is_good && source == upstream_fetcher_.get()) {
173     DVLOG(1) << "Upstream error " << source->GetResponseCode()
174              << " EOR " << end_of_response;
175     FSMEventArgs event_args(EVENT_UPSTREAM_ERROR);
176     DispatchEvent(event_args);
177     return;
178   }
179 
180   // Ignore incoming data on the upstream connection.
181   if (source == upstream_fetcher_.get())
182     return;
183 
184   DCHECK(response_is_good && source == downstream_fetcher_.get());
185 
186   // The downstream response is organized in chunks, whose size is determined
187   // by a 4 bytes prefix, transparently handled by the ChunkedByteBuffer class.
188   // Such chunks are sent by the speech recognition webservice over the HTTP
189   // downstream channel using HTTP chunked transfer (unrelated to our chunks).
190   // This function is called every time an HTTP chunk is received by the
191   // url fetcher. However there isn't any particular matching beween our
192   // protocol chunks and HTTP chunks, in the sense that a single HTTP chunk can
193   // contain a portion of one chunk or even more chunks together.
194   chunked_byte_buffer_.Append(response);
195 
196   // A single HTTP chunk can contain more than one data chunk, thus the while.
197   while (chunked_byte_buffer_.HasChunks()) {
198     FSMEventArgs event_args(EVENT_DOWNSTREAM_RESPONSE);
199     event_args.response = chunked_byte_buffer_.PopChunk();
200     DCHECK(event_args.response.get());
201     DumpResponse(std::string(event_args.response->begin(),
202                              event_args.response->end()));
203     DispatchEvent(event_args);
204   }
205   if (end_of_response) {
206     FSMEventArgs event_args(EVENT_DOWNSTREAM_CLOSED);
207     DispatchEvent(event_args);
208   }
209 }
210 
IsRecognitionPending() const211 bool GoogleStreamingRemoteEngine::IsRecognitionPending() const {
212   DCHECK(CalledOnValidThread());
213   return state_ != STATE_IDLE;
214 }
215 
GetDesiredAudioChunkDurationMs() const216 int GoogleStreamingRemoteEngine::GetDesiredAudioChunkDurationMs() const {
217   return kAudioPacketIntervalMs;
218 }
219 
220 // -----------------------  Core FSM implementation ---------------------------
221 
DispatchEvent(const FSMEventArgs & event_args)222 void GoogleStreamingRemoteEngine::DispatchEvent(
223     const FSMEventArgs& event_args) {
224   DCHECK(CalledOnValidThread());
225   DCHECK_LE(event_args.event, EVENT_MAX_VALUE);
226   DCHECK_LE(state_, STATE_MAX_VALUE);
227 
228   // Event dispatching must be sequential, otherwise it will break all the rules
229   // and the assumptions of the finite state automata model.
230   DCHECK(!is_dispatching_event_);
231   is_dispatching_event_ = true;
232 
233   state_ = ExecuteTransitionAndGetNextState(event_args);
234 
235   is_dispatching_event_ = false;
236 }
237 
238 GoogleStreamingRemoteEngine::FSMState
ExecuteTransitionAndGetNextState(const FSMEventArgs & event_args)239 GoogleStreamingRemoteEngine::ExecuteTransitionAndGetNextState(
240     const FSMEventArgs& event_args) {
241   const FSMEvent event = event_args.event;
242   switch (state_) {
243     case STATE_IDLE:
244       switch (event) {
245         case EVENT_START_RECOGNITION:
246           return ConnectBothStreams(event_args);
247         case EVENT_END_RECOGNITION:
248         // Note AUDIO_CHUNK and AUDIO_END events can remain enqueued in case of
249         // abort, so we just silently drop them here.
250         case EVENT_AUDIO_CHUNK:
251         case EVENT_AUDIO_CHUNKS_ENDED:
252         // DOWNSTREAM_CLOSED can be received if we end up here due to an error.
253         case EVENT_DOWNSTREAM_CLOSED:
254           return DoNothing(event_args);
255         case EVENT_UPSTREAM_ERROR:
256         case EVENT_DOWNSTREAM_ERROR:
257         case EVENT_DOWNSTREAM_RESPONSE:
258           return NotFeasible(event_args);
259       }
260       break;
261     case STATE_BOTH_STREAMS_CONNECTED:
262       switch (event) {
263         case EVENT_AUDIO_CHUNK:
264           return TransmitAudioUpstream(event_args);
265         case EVENT_DOWNSTREAM_RESPONSE:
266           return ProcessDownstreamResponse(event_args);
267         case EVENT_AUDIO_CHUNKS_ENDED:
268           return CloseUpstreamAndWaitForResults(event_args);
269         case EVENT_END_RECOGNITION:
270           return AbortSilently(event_args);
271         case EVENT_UPSTREAM_ERROR:
272         case EVENT_DOWNSTREAM_ERROR:
273         case EVENT_DOWNSTREAM_CLOSED:
274           return AbortWithError(event_args);
275         case EVENT_START_RECOGNITION:
276           return NotFeasible(event_args);
277       }
278       break;
279     case STATE_WAITING_DOWNSTREAM_RESULTS:
280       switch (event) {
281         case EVENT_DOWNSTREAM_RESPONSE:
282           return ProcessDownstreamResponse(event_args);
283         case EVENT_DOWNSTREAM_CLOSED:
284           return RaiseNoMatchErrorIfGotNoResults(event_args);
285         case EVENT_END_RECOGNITION:
286           return AbortSilently(event_args);
287         case EVENT_UPSTREAM_ERROR:
288         case EVENT_DOWNSTREAM_ERROR:
289           return AbortWithError(event_args);
290         case EVENT_START_RECOGNITION:
291         case EVENT_AUDIO_CHUNK:
292         case EVENT_AUDIO_CHUNKS_ENDED:
293           return NotFeasible(event_args);
294       }
295       break;
296   }
297   return NotFeasible(event_args);
298 }
299 
300 // ----------- Contract for all the FSM evolution functions below -------------
301 //  - Are guaranteed to be executed in the same thread (IO, except for tests);
302 //  - Are guaranteed to be not reentrant (themselves and each other);
303 //  - event_args members are guaranteed to be stable during the call;
304 
305 GoogleStreamingRemoteEngine::FSMState
ConnectBothStreams(const FSMEventArgs &)306 GoogleStreamingRemoteEngine::ConnectBothStreams(const FSMEventArgs&) {
307   DCHECK(!upstream_fetcher_.get());
308   DCHECK(!downstream_fetcher_.get());
309 
310   encoder_.reset(AudioEncoder::Create(kDefaultAudioCodec,
311                                       config_.audio_sample_rate,
312                                       config_.audio_num_bits_per_sample));
313   DCHECK(encoder_.get());
314   const std::string request_key = GenerateRequestKey();
315 
316   // Setup downstream fetcher.
317   std::vector<std::string> downstream_args;
318   downstream_args.push_back(
319       "key=" + net::EscapeQueryParamValue(GetAPIKey(), true));
320   downstream_args.push_back("pair=" + request_key);
321   downstream_args.push_back("output=pb");
322   GURL downstream_url(std::string(kWebServiceBaseUrl) +
323                       std::string(kDownstreamUrl) +
324                       JoinString(downstream_args, '&'));
325 
326   downstream_fetcher_.reset(URLFetcher::Create(
327       kDownstreamUrlFetcherIdForTesting, downstream_url, URLFetcher::GET,
328       this));
329   downstream_fetcher_->SetRequestContext(url_context_.get());
330   downstream_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
331                                     net::LOAD_DO_NOT_SEND_COOKIES |
332                                     net::LOAD_DO_NOT_SEND_AUTH_DATA);
333   downstream_fetcher_->Start();
334 
335   // Setup upstream fetcher.
336   // TODO(hans): Support for user-selected grammars.
337   std::vector<std::string> upstream_args;
338   upstream_args.push_back("key=" +
339       net::EscapeQueryParamValue(GetAPIKey(), true));
340   upstream_args.push_back("pair=" + request_key);
341   upstream_args.push_back("output=pb");
342   upstream_args.push_back(
343       "lang=" + net::EscapeQueryParamValue(GetAcceptedLanguages(), true));
344   upstream_args.push_back(
345       config_.filter_profanities ? "pFilter=2" : "pFilter=0");
346   if (config_.max_hypotheses > 0U) {
347     int max_alternatives = std::min(kMaxMaxAlternatives,
348                                     config_.max_hypotheses);
349     upstream_args.push_back("maxAlternatives=" +
350                             base::UintToString(max_alternatives));
351   }
352   upstream_args.push_back("client=chromium");
353   if (!config_.hardware_info.empty()) {
354     upstream_args.push_back(
355         "xhw=" + net::EscapeQueryParamValue(config_.hardware_info, true));
356   }
357   if (config_.continuous)
358     upstream_args.push_back("continuous");
359   if (config_.interim_results)
360     upstream_args.push_back("interim");
361 
362   GURL upstream_url(std::string(kWebServiceBaseUrl) +
363                     std::string(kUpstreamUrl) +
364                     JoinString(upstream_args, '&'));
365 
366   upstream_fetcher_.reset(URLFetcher::Create(
367       kUpstreamUrlFetcherIdForTesting, upstream_url, URLFetcher::POST, this));
368   upstream_fetcher_->SetChunkedUpload(encoder_->mime_type());
369   upstream_fetcher_->SetRequestContext(url_context_.get());
370   upstream_fetcher_->SetReferrer(config_.origin_url);
371   upstream_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
372                                   net::LOAD_DO_NOT_SEND_COOKIES |
373                                   net::LOAD_DO_NOT_SEND_AUTH_DATA);
374   upstream_fetcher_->Start();
375   previous_response_length_ = 0;
376   return STATE_BOTH_STREAMS_CONNECTED;
377 }
378 
379 GoogleStreamingRemoteEngine::FSMState
TransmitAudioUpstream(const FSMEventArgs & event_args)380 GoogleStreamingRemoteEngine::TransmitAudioUpstream(
381     const FSMEventArgs& event_args) {
382   DCHECK(upstream_fetcher_.get());
383   DCHECK(event_args.audio_data.get());
384   const AudioChunk& audio = *(event_args.audio_data.get());
385 
386   DCHECK_EQ(audio.bytes_per_sample(), config_.audio_num_bits_per_sample / 8);
387   encoder_->Encode(audio);
388   scoped_refptr<AudioChunk> encoded_data(encoder_->GetEncodedDataAndClear());
389   upstream_fetcher_->AppendChunkToUpload(encoded_data->AsString(), false);
390   return state_;
391 }
392 
393 GoogleStreamingRemoteEngine::FSMState
ProcessDownstreamResponse(const FSMEventArgs & event_args)394 GoogleStreamingRemoteEngine::ProcessDownstreamResponse(
395     const FSMEventArgs& event_args) {
396   DCHECK(event_args.response.get());
397 
398   proto::SpeechRecognitionEvent ws_event;
399   if (!ws_event.ParseFromString(std::string(event_args.response->begin(),
400                                             event_args.response->end())))
401     return AbortWithError(event_args);
402 
403   // An empty (default) event is used to notify us that the upstream has
404   // been connected. Ignore.
405   if (!ws_event.result_size() && (!ws_event.has_status() ||
406       ws_event.status() == proto::SpeechRecognitionEvent::STATUS_SUCCESS)) {
407     DVLOG(1) << "Received empty response";
408     return state_;
409   }
410 
411   if (ws_event.has_status()) {
412     switch (ws_event.status()) {
413       case proto::SpeechRecognitionEvent::STATUS_SUCCESS:
414         break;
415       case proto::SpeechRecognitionEvent::STATUS_NO_SPEECH:
416         return Abort(SPEECH_RECOGNITION_ERROR_NO_SPEECH);
417       case proto::SpeechRecognitionEvent::STATUS_ABORTED:
418         return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
419       case proto::SpeechRecognitionEvent::STATUS_AUDIO_CAPTURE:
420         return Abort(SPEECH_RECOGNITION_ERROR_AUDIO);
421       case proto::SpeechRecognitionEvent::STATUS_NETWORK:
422         return Abort(SPEECH_RECOGNITION_ERROR_NETWORK);
423       case proto::SpeechRecognitionEvent::STATUS_NOT_ALLOWED:
424         // TODO(hans): We need a better error code for this.
425         return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
426       case proto::SpeechRecognitionEvent::STATUS_SERVICE_NOT_ALLOWED:
427         // TODO(hans): We need a better error code for this.
428         return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
429       case proto::SpeechRecognitionEvent::STATUS_BAD_GRAMMAR:
430         return Abort(SPEECH_RECOGNITION_ERROR_BAD_GRAMMAR);
431       case proto::SpeechRecognitionEvent::STATUS_LANGUAGE_NOT_SUPPORTED:
432         // TODO(hans): We need a better error code for this.
433         return Abort(SPEECH_RECOGNITION_ERROR_ABORTED);
434     }
435   }
436 
437   SpeechRecognitionResults results;
438   for (int i = 0; i < ws_event.result_size(); ++i) {
439     const proto::SpeechRecognitionResult& ws_result = ws_event.result(i);
440     results.push_back(SpeechRecognitionResult());
441     SpeechRecognitionResult& result = results.back();
442     result.is_provisional = !(ws_result.has_final() && ws_result.final());
443 
444     if (!result.is_provisional)
445       got_last_definitive_result_ = true;
446 
447     for (int j = 0; j < ws_result.alternative_size(); ++j) {
448       const proto::SpeechRecognitionAlternative& ws_alternative =
449           ws_result.alternative(j);
450       SpeechRecognitionHypothesis hypothesis;
451       if (ws_alternative.has_confidence())
452         hypothesis.confidence = ws_alternative.confidence();
453       else if (ws_result.has_stability())
454         hypothesis.confidence = ws_result.stability();
455       DCHECK(ws_alternative.has_transcript());
456       // TODO(hans): Perhaps the transcript should be required in the proto?
457       if (ws_alternative.has_transcript())
458         hypothesis.utterance = UTF8ToUTF16(ws_alternative.transcript());
459 
460       result.hypotheses.push_back(hypothesis);
461     }
462   }
463 
464   delegate()->OnSpeechRecognitionEngineResults(results);
465 
466   return state_;
467 }
468 
469 GoogleStreamingRemoteEngine::FSMState
RaiseNoMatchErrorIfGotNoResults(const FSMEventArgs & event_args)470 GoogleStreamingRemoteEngine::RaiseNoMatchErrorIfGotNoResults(
471     const FSMEventArgs& event_args) {
472   if (!got_last_definitive_result_) {
473     // Provide an empty result to notify that recognition is ended with no
474     // errors, yet neither any further results.
475     delegate()->OnSpeechRecognitionEngineResults(SpeechRecognitionResults());
476   }
477   return AbortSilently(event_args);
478 }
479 
480 GoogleStreamingRemoteEngine::FSMState
CloseUpstreamAndWaitForResults(const FSMEventArgs &)481 GoogleStreamingRemoteEngine::CloseUpstreamAndWaitForResults(
482     const FSMEventArgs&) {
483   DCHECK(upstream_fetcher_.get());
484   DCHECK(encoder_.get());
485 
486   DVLOG(1) <<  "Closing upstream.";
487 
488   // The encoder requires a non-empty final buffer. So we encode a packet
489   // of silence in case encoder had no data already.
490   std::vector<short> samples(
491       config_.audio_sample_rate * kAudioPacketIntervalMs / 1000);
492   scoped_refptr<AudioChunk> dummy_chunk =
493       new AudioChunk(reinterpret_cast<uint8*>(&samples[0]),
494                      samples.size() * sizeof(short),
495                      encoder_->bits_per_sample() / 8);
496   encoder_->Encode(*dummy_chunk.get());
497   encoder_->Flush();
498   scoped_refptr<AudioChunk> encoded_dummy_data =
499       encoder_->GetEncodedDataAndClear();
500   DCHECK(!encoded_dummy_data->IsEmpty());
501   encoder_.reset();
502 
503   upstream_fetcher_->AppendChunkToUpload(encoded_dummy_data->AsString(), true);
504   got_last_definitive_result_ = false;
505   return STATE_WAITING_DOWNSTREAM_RESULTS;
506 }
507 
508 GoogleStreamingRemoteEngine::FSMState
CloseDownstream(const FSMEventArgs &)509 GoogleStreamingRemoteEngine::CloseDownstream(const FSMEventArgs&) {
510   DCHECK(!upstream_fetcher_.get());
511   DCHECK(downstream_fetcher_.get());
512 
513   DVLOG(1) <<  "Closing downstream.";
514   downstream_fetcher_.reset();
515   return STATE_IDLE;
516 }
517 
518 GoogleStreamingRemoteEngine::FSMState
AbortSilently(const FSMEventArgs &)519 GoogleStreamingRemoteEngine::AbortSilently(const FSMEventArgs&) {
520   return Abort(SPEECH_RECOGNITION_ERROR_NONE);
521 }
522 
523 GoogleStreamingRemoteEngine::FSMState
AbortWithError(const FSMEventArgs &)524 GoogleStreamingRemoteEngine::AbortWithError(const FSMEventArgs&) {
525   return Abort(SPEECH_RECOGNITION_ERROR_NETWORK);
526 }
527 
Abort(SpeechRecognitionErrorCode error_code)528 GoogleStreamingRemoteEngine::FSMState GoogleStreamingRemoteEngine::Abort(
529     SpeechRecognitionErrorCode error_code) {
530   DVLOG(1) << "Aborting with error " << error_code;
531 
532   if (error_code != SPEECH_RECOGNITION_ERROR_NONE) {
533     delegate()->OnSpeechRecognitionEngineError(
534         SpeechRecognitionError(error_code));
535   }
536   downstream_fetcher_.reset();
537   upstream_fetcher_.reset();
538   encoder_.reset();
539   return STATE_IDLE;
540 }
541 
542 GoogleStreamingRemoteEngine::FSMState
DoNothing(const FSMEventArgs &)543 GoogleStreamingRemoteEngine::DoNothing(const FSMEventArgs&) {
544   return state_;
545 }
546 
547 GoogleStreamingRemoteEngine::FSMState
NotFeasible(const FSMEventArgs & event_args)548 GoogleStreamingRemoteEngine::NotFeasible(const FSMEventArgs& event_args) {
549   NOTREACHED() << "Unfeasible event " << event_args.event
550                << " in state " << state_;
551   return state_;
552 }
553 
GetAcceptedLanguages() const554 std::string GoogleStreamingRemoteEngine::GetAcceptedLanguages() const {
555   std::string langs = config_.language;
556   if (langs.empty() && url_context_.get()) {
557     // If no language is provided then we use the first from the accepted
558     // language list. If this list is empty then it defaults to "en-US".
559     // Example of the contents of this list: "es,en-GB;q=0.8", ""
560     net::URLRequestContext* request_context =
561         url_context_->GetURLRequestContext();
562     DCHECK(request_context);
563     // TODO(pauljensen): GoogleStreamingRemoteEngine should be constructed with
564     // a reference to the HttpUserAgentSettings rather than accessing the
565     // accept language through the URLRequestContext.
566     std::string accepted_language_list = request_context->GetAcceptLanguage();
567     size_t separator = accepted_language_list.find_first_of(",;");
568     if (separator != std::string::npos)
569       langs = accepted_language_list.substr(0, separator);
570   }
571   if (langs.empty())
572     langs = "en-US";
573   return langs;
574 }
575 
576 // TODO(primiano): Is there any utility in the codebase that already does this?
GenerateRequestKey() const577 std::string GoogleStreamingRemoteEngine::GenerateRequestKey() const {
578   const int64 kKeepLowBytes = GG_LONGLONG(0x00000000FFFFFFFF);
579   const int64 kKeepHighBytes = GG_LONGLONG(0xFFFFFFFF00000000);
580 
581   // Just keep the least significant bits of timestamp, in order to reduce
582   // probability of collisions.
583   int64 key = (base::Time::Now().ToInternalValue() & kKeepLowBytes) |
584               (base::RandUint64() & kKeepHighBytes);
585   return base::HexEncode(reinterpret_cast<void*>(&key), sizeof(key));
586 }
587 
FSMEventArgs(FSMEvent event_value)588 GoogleStreamingRemoteEngine::FSMEventArgs::FSMEventArgs(FSMEvent event_value)
589     : event(event_value) {
590 }
591 
~FSMEventArgs()592 GoogleStreamingRemoteEngine::FSMEventArgs::~FSMEventArgs() {
593 }
594 
595 }  // namespace content
596