Home | History | Annotate | Download | only in speech
      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.
     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 
     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 
     86 GoogleStreamingRemoteEngine::~GoogleStreamingRemoteEngine() {}
     87 
     88 void GoogleStreamingRemoteEngine::SetConfig(
     89     const SpeechRecognitionEngineConfig& config) {
     90   config_ = config;
     91 }
     92 
     93 void GoogleStreamingRemoteEngine::StartRecognition() {
     94   FSMEventArgs event_args(EVENT_START_RECOGNITION);
     95   DispatchEvent(event_args);
     96 }
     97 
     98 void GoogleStreamingRemoteEngine::EndRecognition() {
     99   FSMEventArgs event_args(EVENT_END_RECOGNITION);
    100   DispatchEvent(event_args);
    101 }
    102 
    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 
    109 void GoogleStreamingRemoteEngine::AudioChunksEnded() {
    110   FSMEventArgs event_args(EVENT_AUDIO_CHUNKS_ENDED);
    111   DispatchEvent(event_args);
    112 }
    113 
    114 void GoogleStreamingRemoteEngine::OnURLFetchComplete(const URLFetcher* source) {
    115   const bool kResponseComplete = true;
    116   DispatchHTTPResponse(source, kResponseComplete);
    117 }
    118 
    119 void GoogleStreamingRemoteEngine::OnURLFetchDownloadProgress(
    120     const URLFetcher* source, int64 current, int64 total) {
    121   const bool kPartialResponse = false;
    122   DispatchHTTPResponse(source, kPartialResponse);
    123 }
    124 
    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 
    195 bool GoogleStreamingRemoteEngine::IsRecognitionPending() const {
    196   DCHECK(CalledOnValidThread());
    197   return state_ != STATE_IDLE;
    198 }
    199 
    200 int GoogleStreamingRemoteEngine::GetDesiredAudioChunkDurationMs() const {
    201   return kAudioPacketIntervalMs;
    202 }
    203 
    204 // -----------------------  Core FSM implementation ---------------------------
    205 
    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
    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
    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
    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
    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
    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
    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
    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
    503 GoogleStreamingRemoteEngine::AbortSilently(const FSMEventArgs&) {
    504   return Abort(SPEECH_RECOGNITION_ERROR_NONE);
    505 }
    506 
    507 GoogleStreamingRemoteEngine::FSMState
    508 GoogleStreamingRemoteEngine::AbortWithError(const FSMEventArgs&) {
    509   return Abort(SPEECH_RECOGNITION_ERROR_NETWORK);
    510 }
    511 
    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
    527 GoogleStreamingRemoteEngine::DoNothing(const FSMEventArgs&) {
    528   return state_;
    529 }
    530 
    531 GoogleStreamingRemoteEngine::FSMState
    532 GoogleStreamingRemoteEngine::NotFeasible(const FSMEventArgs& event_args) {
    533   NOTREACHED() << "Unfeasible event " << event_args.event
    534                << " in state " << state_;
    535   return state_;
    536 }
    537 
    538 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?
    564 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 
    575 GoogleStreamingRemoteEngine::FSMEventArgs::FSMEventArgs(FSMEvent event_value)
    576     : event(event_value) {
    577 }
    578 
    579 GoogleStreamingRemoteEngine::FSMEventArgs::~FSMEventArgs() {
    580 }
    581 
    582 }  // namespace content
    583