Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / browser / speech / google_streaming_remote_engine.cc
blobf9e2e6ea04dd9864e7eec47366d4d17d50b1944c
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"
7 #include <vector>
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"
28 using net::URLFetcher;
30 namespace content {
31 namespace {
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!";
48 return;
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();
70 } // namespace
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),
84 state_(STATE_IDLE) {}
86 GoogleStreamingRemoteEngine::~GoogleStreamingRemoteEngine() {}
88 void GoogleStreamingRemoteEngine::SetConfig(
89 const SpeechRecognitionEngineConfig& config) {
90 config_ = 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());
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();
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);
154 return;
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;
164 // Ignore incoming data on the upstream connection.
165 if (source == upstream_fetcher_.get())
166 return;
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;
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);
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);
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);
279 break;
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,
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();
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);
374 return state_;
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";
392 return state_;
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);
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);
450 return state_;
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());
481 encoder_->Flush();
482 scoped_refptr<AudioChunk> encoded_dummy_data =
483 encoder_->GetEncodedDataAndClear();
484 DCHECK(!encoded_dummy_data->IsEmpty());
485 encoder_.reset();
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();
499 return STATE_IDLE;
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();
522 encoder_.reset();
523 return STATE_IDLE;
526 GoogleStreamingRemoteEngine::FSMState
527 GoogleStreamingRemoteEngine::DoNothing(const FSMEventArgs&) {
528 return state_;
531 GoogleStreamingRemoteEngine::FSMState
532 GoogleStreamingRemoteEngine::NotFeasible(const FSMEventArgs& event_args) {
533 NOTREACHED() << "Unfeasible event " << event_args.event
534 << " in state " << state_;
535 return 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);
558 if (langs.empty())
559 langs = "en-US";
560 return langs;
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