| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/speech/tts_controller_impl.h" | 5 #include "chrome/browser/speech/tts_controller_impl.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <string> | 9 #include <string> |
| 10 #include <vector> | 10 #include <vector> |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 74 id_(next_utterance_id_++), | 74 id_(next_utterance_id_++), |
| 75 src_id_(-1), | 75 src_id_(-1), |
| 76 gender_(TTS_GENDER_NONE), | 76 gender_(TTS_GENDER_NONE), |
| 77 can_enqueue_(false), | 77 can_enqueue_(false), |
| 78 char_index_(0), | 78 char_index_(0), |
| 79 finished_(false) { | 79 finished_(false) { |
| 80 options_.reset(new base::DictionaryValue()); | 80 options_.reset(new base::DictionaryValue()); |
| 81 } | 81 } |
| 82 | 82 |
| 83 Utterance::~Utterance() { | 83 Utterance::~Utterance() { |
| 84 DCHECK(finished_); | 84 // It's an error if an Utterance is destructed without being finished, |
| 85 // unless |browser_context_| is nullptr because it's a unit test. |
| 86 DCHECK(finished_ || !browser_context_); |
| 85 } | 87 } |
| 86 | 88 |
| 87 void Utterance::OnTtsEvent(TtsEventType event_type, | 89 void Utterance::OnTtsEvent(TtsEventType event_type, |
| 88 int char_index, | 90 int char_index, |
| 89 const std::string& error_message) { | 91 const std::string& error_message) { |
| 90 if (char_index >= 0) | 92 if (char_index >= 0) |
| 91 char_index_ = char_index; | 93 char_index_ = char_index; |
| 92 if (IsFinalTtsEventType(event_type)) | 94 if (IsFinalTtsEventType(event_type)) |
| 93 finished_ = true; | 95 finished_ = true; |
| 94 | 96 |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 156 | 158 |
| 157 void TtsControllerImpl::SpeakNow(Utterance* utterance) { | 159 void TtsControllerImpl::SpeakNow(Utterance* utterance) { |
| 158 // Ensure we have all built-in voices loaded. This is a no-op if already | 160 // Ensure we have all built-in voices loaded. This is a no-op if already |
| 159 // loaded. | 161 // loaded. |
| 160 bool loaded_built_in = | 162 bool loaded_built_in = |
| 161 GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->browser_context()); | 163 GetPlatformImpl()->LoadBuiltInTtsExtension(utterance->browser_context()); |
| 162 | 164 |
| 163 // Get all available voices and try to find a matching voice. | 165 // Get all available voices and try to find a matching voice. |
| 164 std::vector<VoiceData> voices; | 166 std::vector<VoiceData> voices; |
| 165 GetVoices(utterance->browser_context(), &voices); | 167 GetVoices(utterance->browser_context(), &voices); |
| 168 |
| 169 // Get the best matching voice. If nothing matches, just set "native" |
| 170 // to true because that might trigger deferred loading of native voices. |
| 166 int index = GetMatchingVoice(utterance, voices); | 171 int index = GetMatchingVoice(utterance, voices); |
| 167 | |
| 168 VoiceData voice; | 172 VoiceData voice; |
| 169 if (index != -1) { | 173 if (index >= 0) |
| 170 // Select the matching voice. | |
| 171 voice = voices[index]; | 174 voice = voices[index]; |
| 172 } else { | 175 else |
| 173 // However, if no match was found on a platform without native tts voices, | 176 voice.native = true; // Try to let |
| 174 // attempt to get a voice based only on the current locale without respect | |
| 175 // to any supplied voice names. | |
| 176 std::vector<VoiceData> native_voices; | |
| 177 | |
| 178 if (GetPlatformImpl()->PlatformImplAvailable()) | |
| 179 GetPlatformImpl()->GetVoices(&native_voices); | |
| 180 | |
| 181 if (native_voices.empty() && !voices.empty()) { | |
| 182 // TODO(dtseng): Notify extension caller of an error. | |
| 183 utterance->set_voice_name(""); | |
| 184 // TODO(gaochun): Replace the global variable g_browser_process with | |
| 185 // GetContentClient()->browser() to eliminate the dependency of browser | |
| 186 // once TTS implementation was moved to content. | |
| 187 utterance->set_lang(g_browser_process->GetApplicationLocale()); | |
| 188 index = GetMatchingVoice(utterance, voices); | |
| 189 | |
| 190 // If even that fails, just take the first available voice. | |
| 191 if (index == -1) | |
| 192 index = 0; | |
| 193 voice = voices[index]; | |
| 194 } else { | |
| 195 // Otherwise, simply give native voices a chance to handle this utterance. | |
| 196 voice.native = true; | |
| 197 } | |
| 198 } | |
| 199 | 177 |
| 200 GetPlatformImpl()->WillSpeakUtteranceWithVoice(utterance, voice); | 178 GetPlatformImpl()->WillSpeakUtteranceWithVoice(utterance, voice); |
| 201 | 179 |
| 202 if (!voice.native) { | 180 if (!voice.native) { |
| 203 #if !defined(OS_ANDROID) | 181 #if !defined(OS_ANDROID) |
| 204 DCHECK(!voice.extension_id.empty()); | 182 DCHECK(!voice.extension_id.empty()); |
| 205 current_utterance_ = utterance; | 183 current_utterance_ = utterance; |
| 206 utterance->set_extension_id(voice.extension_id); | 184 utterance->set_extension_id(voice.extension_id); |
| 207 if (tts_engine_delegate_) | 185 if (tts_engine_delegate_) |
| 208 tts_engine_delegate_->Speak(utterance, voice); | 186 tts_engine_delegate_->Speak(utterance, voice); |
| (...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 369 } | 347 } |
| 370 | 348 |
| 371 TtsPlatformImpl* TtsControllerImpl::GetPlatformImpl() { | 349 TtsPlatformImpl* TtsControllerImpl::GetPlatformImpl() { |
| 372 if (!platform_impl_) | 350 if (!platform_impl_) |
| 373 platform_impl_ = TtsPlatformImpl::GetInstance(); | 351 platform_impl_ = TtsPlatformImpl::GetInstance(); |
| 374 return platform_impl_; | 352 return platform_impl_; |
| 375 } | 353 } |
| 376 | 354 |
| 377 int TtsControllerImpl::GetMatchingVoice( | 355 int TtsControllerImpl::GetMatchingVoice( |
| 378 const Utterance* utterance, std::vector<VoiceData>& voices) { | 356 const Utterance* utterance, std::vector<VoiceData>& voices) { |
| 379 // Make two passes: the first time, do strict language matching | 357 // Return the index of the voice that best match the utterance parameters. |
| 380 // ('fr-FR' does not match 'fr-CA'). The second time, do prefix | 358 // |
| 381 // language matching ('fr-FR' matches 'fr' and 'fr-CA') | 359 // These criteria are considered mandatory - if they're specified, any voice |
| 382 for (int pass = 0; pass < 2; ++pass) { | 360 // that doesn't match is rejected. |
| 383 for (size_t i = 0; i < voices.size(); ++i) { | 361 // |
| 384 const VoiceData& voice = voices[i]; | 362 // Extension ID |
| 363 // Voice name |
| 364 // |
| 365 // The other criteria are scored based on how well they match, in |
| 366 // this order of precedence: |
| 367 // |
| 368 // Utterange language (exact region preferred, then general language) |
| 369 // App/system language (exact region preferred, then general language) |
| 370 // Required event types |
| 371 // Gender |
| 385 | 372 |
| 386 if (!utterance->extension_id().empty() && | 373 // TODO(gaochun): Replace the global variable g_browser_process with |
| 387 utterance->extension_id() != voice.extension_id) { | 374 // GetContentClient()->browser() to eliminate the dependency of browser |
| 388 continue; | 375 // once TTS implementation was moved to content. |
| 376 std::string app_lang = g_browser_process->GetApplicationLocale(); |
| 377 |
| 378 // Start with a best score of -1, that way even if none of the criteria |
| 379 // match, something will be returned if there are any voices. |
| 380 int best_score = -1; |
| 381 int best_score_index = -1; |
| 382 for (size_t i = 0; i < voices.size(); ++i) { |
| 383 const VoiceData& voice = voices[i]; |
| 384 int score = 0; |
| 385 |
| 386 // If the extension ID is specified, check for an exact match. |
| 387 if (!utterance->extension_id().empty() && |
| 388 utterance->extension_id() != voice.extension_id) |
| 389 continue; |
| 390 |
| 391 // If the voice name is specified, check for an exact match. |
| 392 if (!utterance->voice_name().empty() && |
| 393 voice.name != utterance->voice_name()) |
| 394 continue; |
| 395 |
| 396 // Prefer the utterance language. |
| 397 if (!voice.lang.empty() && !utterance->lang().empty()) { |
| 398 // An exact language match is worth more than a partial match. |
| 399 if (voice.lang == utterance->lang()) { |
| 400 score += 32; |
| 401 } else if (TrimLanguageCode(voice.lang) == |
| 402 TrimLanguageCode(utterance->lang())) { |
| 403 score += 16; |
| 389 } | 404 } |
| 405 } |
| 390 | 406 |
| 391 if (!voice.name.empty() && | 407 // Prefer the system language after that. |
| 392 !utterance->voice_name().empty() && | 408 if (!voice.lang.empty()) { |
| 393 voice.name != utterance->voice_name()) { | 409 if (voice.lang == app_lang) |
| 394 continue; | 410 score += 8; |
| 395 } | 411 else if (TrimLanguageCode(voice.lang) == TrimLanguageCode(app_lang)) |
| 396 if (!voice.lang.empty() && !utterance->lang().empty()) { | 412 score += 4; |
| 397 std::string voice_lang = voice.lang; | 413 } |
| 398 std::string utterance_lang = utterance->lang(); | 414 |
| 399 if (pass == 1) { | 415 // Next, prefer required event types. |
| 400 voice_lang = TrimLanguageCode(voice_lang); | 416 if (utterance->required_event_types().size() > 0) { |
| 401 utterance_lang = TrimLanguageCode(utterance_lang); | 417 bool has_all_required_event_types = true; |
| 402 } | 418 for (std::set<TtsEventType>::const_iterator iter = |
| 403 if (voice_lang != utterance_lang) { | 419 utterance->required_event_types().begin(); |
| 404 continue; | 420 iter != utterance->required_event_types().end(); |
| 421 ++iter) { |
| 422 if (voice.events.find(*iter) == voice.events.end()) { |
| 423 has_all_required_event_types = false; |
| 424 break; |
| 405 } | 425 } |
| 406 } | 426 } |
| 407 if (voice.gender != TTS_GENDER_NONE && | 427 if (has_all_required_event_types) |
| 408 utterance->gender() != TTS_GENDER_NONE && | 428 score += 2; |
| 409 voice.gender != utterance->gender()) { | 429 } |
| 410 continue; | |
| 411 } | |
| 412 | 430 |
| 413 if (utterance->required_event_types().size() > 0) { | 431 // Finally prefer the requested gender last. |
| 414 bool has_all_required_event_types = true; | 432 if (voice.gender != TTS_GENDER_NONE && |
| 415 for (std::set<TtsEventType>::const_iterator iter = | 433 utterance->gender() != TTS_GENDER_NONE && |
| 416 utterance->required_event_types().begin(); | 434 voice.gender == utterance->gender()) { |
| 417 iter != utterance->required_event_types().end(); | 435 score += 1; |
| 418 ++iter) { | 436 } |
| 419 if (voice.events.find(*iter) == voice.events.end()) { | |
| 420 has_all_required_event_types = false; | |
| 421 break; | |
| 422 } | |
| 423 } | |
| 424 if (!has_all_required_event_types) | |
| 425 continue; | |
| 426 } | |
| 427 | 437 |
| 428 return static_cast<int>(i); | 438 if (score > best_score) { |
| 439 best_score = score; |
| 440 best_score_index = i; |
| 429 } | 441 } |
| 430 } | 442 } |
| 431 | 443 |
| 432 return -1; | 444 return best_score_index; |
| 433 } | 445 } |
| 434 | 446 |
| 435 void TtsControllerImpl::VoicesChanged() { | 447 void TtsControllerImpl::VoicesChanged() { |
| 436 // Existence of platform tts indicates explicit requests to tts. Since | 448 // Existence of platform tts indicates explicit requests to tts. Since |
| 437 // |VoicesChanged| can occur implicitly, only send if needed. | 449 // |VoicesChanged| can occur implicitly, only send if needed. |
| 438 if (!platform_impl_) | 450 if (!platform_impl_) |
| 439 return; | 451 return; |
| 440 | 452 |
| 441 for (std::set<VoicesChangedDelegate*>::iterator iter = | 453 for (std::set<VoicesChangedDelegate*>::iterator iter = |
| 442 voices_changed_delegates_.begin(); | 454 voices_changed_delegates_.begin(); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 486 } | 498 } |
| 487 | 499 |
| 488 void TtsControllerImpl::SetTtsEngineDelegate( | 500 void TtsControllerImpl::SetTtsEngineDelegate( |
| 489 TtsEngineDelegate* delegate) { | 501 TtsEngineDelegate* delegate) { |
| 490 tts_engine_delegate_ = delegate; | 502 tts_engine_delegate_ = delegate; |
| 491 } | 503 } |
| 492 | 504 |
| 493 TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() { | 505 TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() { |
| 494 return tts_engine_delegate_; | 506 return tts_engine_delegate_; |
| 495 } | 507 } |
| OLD | NEW |