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