Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / speech / tts_linux.cc
blob05360b1868aa05a8b6df55abf60a3eea31d80321
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 <math.h>
7 #include <map>
9 #include "base/command_line.h"
10 #include "base/debug/leak_annotations.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/memory/singleton.h"
13 #include "base/synchronization/lock.h"
14 #include "chrome/browser/speech/tts_platform.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/common/content_switches.h"
18 #include "library_loaders/libspeechd.h"
20 using content::BrowserThread;
22 namespace {
24 const char kNotSupportedError[] =
25 "Native speech synthesis not supported on this platform.";
27 struct SPDChromeVoice {
28 std::string name;
29 std::string module;
32 } // namespace
34 class TtsPlatformImplLinux : public TtsPlatformImpl {
35 public:
36 bool PlatformImplAvailable() override;
37 bool Speak(int utterance_id,
38 const std::string& utterance,
39 const std::string& lang,
40 const VoiceData& voice,
41 const UtteranceContinuousParameters& params) override;
42 bool StopSpeaking() override;
43 void Pause() override;
44 void Resume() override;
45 bool IsSpeaking() override;
46 void GetVoices(std::vector<VoiceData>* out_voices) override;
48 void OnSpeechEvent(SPDNotificationType type);
50 // Get the single instance of this class.
51 static TtsPlatformImplLinux* GetInstance();
53 private:
54 TtsPlatformImplLinux();
55 ~TtsPlatformImplLinux() override;
57 // Initiate the connection with the speech dispatcher.
58 void Initialize();
60 // Resets the connection with speech dispatcher.
61 void Reset();
63 static void NotificationCallback(size_t msg_id,
64 size_t client_id,
65 SPDNotificationType type);
67 static void IndexMarkCallback(size_t msg_id,
68 size_t client_id,
69 SPDNotificationType state,
70 char* index_mark);
72 static SPDNotificationType current_notification_;
74 base::Lock initialization_lock_;
75 LibSpeechdLoader libspeechd_loader_;
76 SPDConnection* conn_;
78 // These apply to the current utterance only.
79 std::string utterance_;
80 int utterance_id_;
82 // Map a string composed of a voicename and module to the voicename. Used to
83 // uniquely identify a voice across all available modules.
84 scoped_ptr<std::map<std::string, SPDChromeVoice> > all_native_voices_;
86 friend struct DefaultSingletonTraits<TtsPlatformImplLinux>;
88 DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux);
91 // static
92 SPDNotificationType TtsPlatformImplLinux::current_notification_ =
93 SPD_EVENT_END;
95 TtsPlatformImplLinux::TtsPlatformImplLinux()
96 : utterance_id_(0) {
97 const base::CommandLine& command_line =
98 *base::CommandLine::ForCurrentProcess();
99 if (!command_line.HasSwitch(switches::kEnableSpeechDispatcher))
100 return;
102 BrowserThread::PostTask(BrowserThread::FILE,
103 FROM_HERE,
104 base::Bind(&TtsPlatformImplLinux::Initialize,
105 base::Unretained(this)));
108 void TtsPlatformImplLinux::Initialize() {
109 base::AutoLock lock(initialization_lock_);
111 if (!libspeechd_loader_.Load("libspeechd.so.2"))
112 return;
115 // spd_open has memory leaks which are hard to suppress.
116 // http://crbug.com/317360
117 ANNOTATE_SCOPED_MEMORY_LEAK;
118 conn_ = libspeechd_loader_.spd_open(
119 "chrome", "extension_api", NULL, SPD_MODE_THREADED);
121 if (!conn_)
122 return;
124 // Register callbacks for all events.
125 conn_->callback_begin =
126 conn_->callback_end =
127 conn_->callback_cancel =
128 conn_->callback_pause =
129 conn_->callback_resume =
130 &NotificationCallback;
132 conn_->callback_im = &IndexMarkCallback;
134 libspeechd_loader_.spd_set_notification_on(conn_, SPD_BEGIN);
135 libspeechd_loader_.spd_set_notification_on(conn_, SPD_END);
136 libspeechd_loader_.spd_set_notification_on(conn_, SPD_CANCEL);
137 libspeechd_loader_.spd_set_notification_on(conn_, SPD_PAUSE);
138 libspeechd_loader_.spd_set_notification_on(conn_, SPD_RESUME);
141 TtsPlatformImplLinux::~TtsPlatformImplLinux() {
142 base::AutoLock lock(initialization_lock_);
143 if (conn_) {
144 libspeechd_loader_.spd_close(conn_);
145 conn_ = NULL;
149 void TtsPlatformImplLinux::Reset() {
150 base::AutoLock lock(initialization_lock_);
151 if (conn_)
152 libspeechd_loader_.spd_close(conn_);
153 conn_ = libspeechd_loader_.spd_open(
154 "chrome", "extension_api", NULL, SPD_MODE_THREADED);
157 bool TtsPlatformImplLinux::PlatformImplAvailable() {
158 if (!initialization_lock_.Try())
159 return false;
160 bool result = libspeechd_loader_.loaded() && (conn_ != NULL);
161 initialization_lock_.Release();
162 return result;
165 bool TtsPlatformImplLinux::Speak(
166 int utterance_id,
167 const std::string& utterance,
168 const std::string& lang,
169 const VoiceData& voice,
170 const UtteranceContinuousParameters& params) {
171 if (!PlatformImplAvailable()) {
172 error_ = kNotSupportedError;
173 return false;
176 // Speech dispatcher's speech params are around 3x at either limit.
177 float rate = params.rate > 3 ? 3 : params.rate;
178 rate = params.rate < 0.334 ? 0.334 : rate;
179 float pitch = params.pitch > 3 ? 3 : params.pitch;
180 pitch = params.pitch < 0.334 ? 0.334 : pitch;
182 std::map<std::string, SPDChromeVoice>::iterator it =
183 all_native_voices_->find(voice.name);
184 if (it != all_native_voices_->end()) {
185 libspeechd_loader_.spd_set_output_module(conn_, it->second.module.c_str());
186 libspeechd_loader_.spd_set_synthesis_voice(conn_, it->second.name.c_str());
189 // Map our multiplicative range to Speech Dispatcher's linear range.
190 // .334 = -100.
191 // 3 = 100.
192 libspeechd_loader_.spd_set_voice_rate(conn_, 100 * log10(rate) / log10(3));
193 libspeechd_loader_.spd_set_voice_pitch(conn_, 100 * log10(pitch) / log10(3));
195 // Support languages other than the default
196 if (!lang.empty())
197 libspeechd_loader_.spd_set_language(conn_, lang.c_str());
199 utterance_ = utterance;
200 utterance_id_ = utterance_id;
202 if (libspeechd_loader_.spd_say(conn_, SPD_TEXT, utterance.c_str()) == -1) {
203 Reset();
204 return false;
206 return true;
209 bool TtsPlatformImplLinux::StopSpeaking() {
210 if (!PlatformImplAvailable())
211 return false;
212 if (libspeechd_loader_.spd_stop(conn_) == -1) {
213 Reset();
214 return false;
216 return true;
219 void TtsPlatformImplLinux::Pause() {
220 if (!PlatformImplAvailable())
221 return;
222 libspeechd_loader_.spd_pause(conn_);
225 void TtsPlatformImplLinux::Resume() {
226 if (!PlatformImplAvailable())
227 return;
228 libspeechd_loader_.spd_resume(conn_);
231 bool TtsPlatformImplLinux::IsSpeaking() {
232 return current_notification_ == SPD_EVENT_BEGIN;
235 void TtsPlatformImplLinux::GetVoices(
236 std::vector<VoiceData>* out_voices) {
237 if (!all_native_voices_.get()) {
238 all_native_voices_.reset(new std::map<std::string, SPDChromeVoice>());
239 char** modules = libspeechd_loader_.spd_list_modules(conn_);
240 if (!modules)
241 return;
242 for (int i = 0; modules[i]; i++) {
243 char* module = modules[i];
244 libspeechd_loader_.spd_set_output_module(conn_, module);
245 SPDVoice** native_voices =
246 libspeechd_loader_.spd_list_synthesis_voices(conn_);
247 if (!native_voices) {
248 free(module);
249 continue;
251 for (int j = 0; native_voices[j]; j++) {
252 SPDVoice* native_voice = native_voices[j];
253 SPDChromeVoice native_data;
254 native_data.name = native_voice->name;
255 native_data.module = module;
256 std::string key;
257 key.append(native_data.name);
258 key.append(" ");
259 key.append(native_data.module);
260 all_native_voices_->insert(
261 std::pair<std::string, SPDChromeVoice>(key, native_data));
262 free(native_voices[j]);
264 free(modules[i]);
268 for (std::map<std::string, SPDChromeVoice>::iterator it =
269 all_native_voices_->begin();
270 it != all_native_voices_->end();
271 it++) {
272 out_voices->push_back(VoiceData());
273 VoiceData& voice = out_voices->back();
274 voice.native = true;
275 voice.name = it->first;
276 voice.events.insert(TTS_EVENT_START);
277 voice.events.insert(TTS_EVENT_END);
278 voice.events.insert(TTS_EVENT_CANCELLED);
279 voice.events.insert(TTS_EVENT_MARKER);
280 voice.events.insert(TTS_EVENT_PAUSE);
281 voice.events.insert(TTS_EVENT_RESUME);
285 void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) {
286 TtsController* controller = TtsController::GetInstance();
287 switch (type) {
288 case SPD_EVENT_BEGIN:
289 controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string());
290 break;
291 case SPD_EVENT_RESUME:
292 controller->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME, 0, std::string());
293 break;
294 case SPD_EVENT_END:
295 controller->OnTtsEvent(
296 utterance_id_, TTS_EVENT_END, utterance_.size(), std::string());
297 break;
298 case SPD_EVENT_PAUSE:
299 controller->OnTtsEvent(
300 utterance_id_, TTS_EVENT_PAUSE, utterance_.size(), std::string());
301 break;
302 case SPD_EVENT_CANCEL:
303 controller->OnTtsEvent(
304 utterance_id_, TTS_EVENT_CANCELLED, 0, std::string());
305 break;
306 case SPD_EVENT_INDEX_MARK:
307 controller->OnTtsEvent(utterance_id_, TTS_EVENT_MARKER, 0, std::string());
308 break;
312 // static
313 void TtsPlatformImplLinux::NotificationCallback(
314 size_t msg_id, size_t client_id, SPDNotificationType type) {
315 // We run Speech Dispatcher in threaded mode, so these callbacks should always
316 // be in a separate thread.
317 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
318 current_notification_ = type;
319 BrowserThread::PostTask(
320 BrowserThread::UI,
321 FROM_HERE,
322 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
323 base::Unretained(TtsPlatformImplLinux::GetInstance()),
324 type));
328 // static
329 void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id,
330 size_t client_id,
331 SPDNotificationType state,
332 char* index_mark) {
333 // TODO(dtseng): index_mark appears to specify an index type supplied by a
334 // client. Need to explore how this is used before hooking it up with existing
335 // word, sentence events.
336 // We run Speech Dispatcher in threaded mode, so these callbacks should always
337 // be in a separate thread.
338 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
339 current_notification_ = state;
340 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
341 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent,
342 base::Unretained(TtsPlatformImplLinux::GetInstance()),
343 state));
347 // static
348 TtsPlatformImplLinux* TtsPlatformImplLinux::GetInstance() {
349 return Singleton<TtsPlatformImplLinux,
350 LeakySingletonTraits<TtsPlatformImplLinux> >::get();
353 // static
354 TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
355 return TtsPlatformImplLinux::GetInstance();