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.
5 #include "content/browser/speech/google_streaming_remote_engine.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"
28 using net::URLFetcher
;
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
;
39 // This matches the maximum maxAlternatives value supported by the server.
40 const uint32 kMaxMaxAlternatives
= 30;
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!";
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
);
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
=
62 if (alt
.has_confidence())
63 DVLOG(1) << " CONFIDENCE:\t" << alt
.confidence();
64 if (alt
.has_transcript())
65 DVLOG(1) << " TRANSCRIPT:\t" << alt
.transcript();
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;
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),
86 GoogleStreamingRemoteEngine::~GoogleStreamingRemoteEngine() {}
88 void GoogleStreamingRemoteEngine::SetConfig(
89 const SpeechRecognitionEngineConfig
& config
) {
93 void GoogleStreamingRemoteEngine::StartRecognition() {
94 FSMEventArgs
event_args(EVENT_START_RECOGNITION
);
95 DispatchEvent(event_args
);
98 void GoogleStreamingRemoteEngine::EndRecognition() {
99 FSMEventArgs
event_args(EVENT_END_RECOGNITION
);
100 DispatchEvent(event_args
);
103 void GoogleStreamingRemoteEngine::TakeAudioChunk(const AudioChunk
& data
) {
104 FSMEventArgs
event_args(EVENT_AUDIO_CHUNK
);
105 event_args
.audio_data
= &data
;
106 DispatchEvent(event_args
);
109 void GoogleStreamingRemoteEngine::AudioChunksEnded() {
110 FSMEventArgs
event_args(EVENT_AUDIO_CHUNKS_ENDED
);
111 DispatchEvent(event_args
);
114 void GoogleStreamingRemoteEngine::OnURLFetchComplete(const URLFetcher
* source
) {
115 const bool kResponseComplete
= true;
116 DispatchHTTPResponse(source
, kResponseComplete
);
119 void GoogleStreamingRemoteEngine::OnURLFetchDownloadProgress(
120 const URLFetcher
* source
, int64 current
, int64 total
) {
121 const bool kPartialResponse
= false;
122 DispatchHTTPResponse(source
, kPartialResponse
);
125 void GoogleStreamingRemoteEngine::DispatchHTTPResponse(const URLFetcher
* source
,
126 bool end_of_response
) {
127 DCHECK(CalledOnValidThread());
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();
136 DVLOG(1) << (source
== downstream_fetcher_
.get() ? "Downstream" : "Upstream")
137 << "HTTP, code: " << source
->GetResponseCode()
138 << " length: " << current_response_length
139 << " eor: " << end_of_response
;
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
;
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
);
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
);
164 // Ignore incoming data on the upstream connection.
165 if (source
== upstream_fetcher_
.get())
168 DCHECK(response_is_good
&& source
== downstream_fetcher_
.get());
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
);
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
);
189 if (end_of_response
) {
190 FSMEventArgs
event_args(EVENT_DOWNSTREAM_CLOSED
);
191 DispatchEvent(event_args
);
195 bool GoogleStreamingRemoteEngine::IsRecognitionPending() const {
196 DCHECK(CalledOnValidThread());
197 return state_
!= STATE_IDLE
;
200 int GoogleStreamingRemoteEngine::GetDesiredAudioChunkDurationMs() const {
201 return kAudioPacketIntervalMs
;
204 // ----------------------- Core FSM implementation ---------------------------
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
);
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;
217 state_
= ExecuteTransitionAndGetNextState(event_args
);
219 is_dispatching_event_
= false;
222 GoogleStreamingRemoteEngine::FSMState
223 GoogleStreamingRemoteEngine::ExecuteTransitionAndGetNextState(
224 const FSMEventArgs
& event_args
) {
225 const FSMEvent event
= event_args
.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
);
245 case STATE_BOTH_STREAMS_CONNECTED
:
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
);
263 case STATE_WAITING_DOWNSTREAM_RESULTS
:
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
);
281 return NotFeasible(event_args
);
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;
289 GoogleStreamingRemoteEngine::FSMState
290 GoogleStreamingRemoteEngine::ConnectBothStreams(const FSMEventArgs
&) {
291 DCHECK(!upstream_fetcher_
.get());
292 DCHECK(!downstream_fetcher_
.get());
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();
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
, '&'));
310 downstream_fetcher_
.reset(URLFetcher::Create(
311 kDownstreamUrlFetcherIdForTesting
, downstream_url
, URLFetcher::GET
,
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();
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
));
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));
341 if (config_
.continuous
)
342 upstream_args
.push_back("continuous");
343 if (config_
.interim_results
)
344 upstream_args
.push_back("interim");
346 GURL
upstream_url(std::string(kWebServiceBaseUrl
) +
347 std::string(kUpstreamUrl
) +
348 JoinString(upstream_args
, '&'));
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
;
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());
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);
377 GoogleStreamingRemoteEngine::FSMState
378 GoogleStreamingRemoteEngine::ProcessDownstreamResponse(
379 const FSMEventArgs
& event_args
) {
380 DCHECK(event_args
.response
.get());
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
);
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";
395 if (ws_event
.has_status()) {
396 switch (ws_event
.status()) {
397 case proto::SpeechRecognitionEvent::STATUS_SUCCESS
:
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
);
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());
428 if (!result
.is_provisional
)
429 got_last_definitive_result_
= true;
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());
444 result
.hypotheses
.push_back(hypothesis
);
448 delegate()->OnSpeechRecognitionEngineResults(results
);
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());
461 return AbortSilently(event_args
);
464 GoogleStreamingRemoteEngine::FSMState
465 GoogleStreamingRemoteEngine::CloseUpstreamAndWaitForResults(
466 const FSMEventArgs
&) {
467 DCHECK(upstream_fetcher_
.get());
468 DCHECK(encoder_
.get());
470 DVLOG(1) << "Closing upstream.";
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());
482 scoped_refptr
<AudioChunk
> encoded_dummy_data
=
483 encoder_
->GetEncodedDataAndClear();
484 DCHECK(!encoded_dummy_data
->IsEmpty());
487 upstream_fetcher_
->AppendChunkToUpload(encoded_dummy_data
->AsString(), true);
488 got_last_definitive_result_
= false;
489 return STATE_WAITING_DOWNSTREAM_RESULTS
;
492 GoogleStreamingRemoteEngine::FSMState
493 GoogleStreamingRemoteEngine::CloseDownstream(const FSMEventArgs
&) {
494 DCHECK(!upstream_fetcher_
.get());
495 DCHECK(downstream_fetcher_
.get());
497 DVLOG(1) << "Closing downstream.";
498 downstream_fetcher_
.reset();
502 GoogleStreamingRemoteEngine::FSMState
503 GoogleStreamingRemoteEngine::AbortSilently(const FSMEventArgs
&) {
504 return Abort(SPEECH_RECOGNITION_ERROR_NONE
);
507 GoogleStreamingRemoteEngine::FSMState
508 GoogleStreamingRemoteEngine::AbortWithError(const FSMEventArgs
&) {
509 return Abort(SPEECH_RECOGNITION_ERROR_NETWORK
);
512 GoogleStreamingRemoteEngine::FSMState
GoogleStreamingRemoteEngine::Abort(
513 SpeechRecognitionErrorCode error_code
) {
514 DVLOG(1) << "Aborting with error " << error_code
;
516 if (error_code
!= SPEECH_RECOGNITION_ERROR_NONE
) {
517 delegate()->OnSpeechRecognitionEngineError(
518 SpeechRecognitionError(error_code
));
520 downstream_fetcher_
.reset();
521 upstream_fetcher_
.reset();
526 GoogleStreamingRemoteEngine::FSMState
527 GoogleStreamingRemoteEngine::DoNothing(const FSMEventArgs
&) {
531 GoogleStreamingRemoteEngine::FSMState
532 GoogleStreamingRemoteEngine::NotFeasible(const FSMEventArgs
& event_args
) {
533 NOTREACHED() << "Unfeasible event " << event_args
.event
534 << " in state " << state_
;
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
);
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
;
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
));
575 GoogleStreamingRemoteEngine::FSMEventArgs::FSMEventArgs(FSMEvent event_value
)
576 : event(event_value
) {
579 GoogleStreamingRemoteEngine::FSMEventArgs::~FSMEventArgs() {
582 } // namespace content