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.
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
;
24 const char kNotSupportedError
[] =
25 "Native speech synthesis not supported on this platform.";
27 struct SPDChromeVoice
{
34 class TtsPlatformImplLinux
: public TtsPlatformImpl
{
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();
54 TtsPlatformImplLinux();
55 ~TtsPlatformImplLinux() override
;
57 // Initiate the connection with the speech dispatcher.
60 // Resets the connection with speech dispatcher.
63 static void NotificationCallback(size_t msg_id
,
65 SPDNotificationType type
);
67 static void IndexMarkCallback(size_t msg_id
,
69 SPDNotificationType state
,
72 static SPDNotificationType current_notification_
;
74 base::Lock initialization_lock_
;
75 LibSpeechdLoader libspeechd_loader_
;
78 // These apply to the current utterance only.
79 std::string utterance_
;
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
);
92 SPDNotificationType
TtsPlatformImplLinux::current_notification_
=
95 TtsPlatformImplLinux::TtsPlatformImplLinux()
97 const CommandLine
& command_line
= *CommandLine::ForCurrentProcess();
98 if (!command_line
.HasSwitch(switches::kEnableSpeechDispatcher
))
101 BrowserThread::PostTask(BrowserThread::FILE,
103 base::Bind(&TtsPlatformImplLinux::Initialize
,
104 base::Unretained(this)));
107 void TtsPlatformImplLinux::Initialize() {
108 base::AutoLock
lock(initialization_lock_
);
110 if (!libspeechd_loader_
.Load("libspeechd.so.2"))
114 // spd_open has memory leaks which are hard to suppress.
115 // http://crbug.com/317360
116 ANNOTATE_SCOPED_MEMORY_LEAK
;
117 conn_
= libspeechd_loader_
.spd_open(
118 "chrome", "extension_api", NULL
, SPD_MODE_SINGLE
);
123 // Register callbacks for all events.
124 conn_
->callback_begin
=
125 conn_
->callback_end
=
126 conn_
->callback_cancel
=
127 conn_
->callback_pause
=
128 conn_
->callback_resume
=
129 &NotificationCallback
;
131 conn_
->callback_im
= &IndexMarkCallback
;
133 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_BEGIN
);
134 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_END
);
135 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_CANCEL
);
136 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_PAUSE
);
137 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_RESUME
);
140 TtsPlatformImplLinux::~TtsPlatformImplLinux() {
141 base::AutoLock
lock(initialization_lock_
);
143 libspeechd_loader_
.spd_close(conn_
);
148 void TtsPlatformImplLinux::Reset() {
149 base::AutoLock
lock(initialization_lock_
);
151 libspeechd_loader_
.spd_close(conn_
);
152 conn_
= libspeechd_loader_
.spd_open(
153 "chrome", "extension_api", NULL
, SPD_MODE_SINGLE
);
156 bool TtsPlatformImplLinux::PlatformImplAvailable() {
157 if (!initialization_lock_
.Try())
159 bool result
= libspeechd_loader_
.loaded() && (conn_
!= NULL
);
160 initialization_lock_
.Release();
164 bool TtsPlatformImplLinux::Speak(
166 const std::string
& utterance
,
167 const std::string
& lang
,
168 const VoiceData
& voice
,
169 const UtteranceContinuousParameters
& params
) {
170 if (!PlatformImplAvailable()) {
171 error_
= kNotSupportedError
;
175 // Speech dispatcher's speech params are around 3x at either limit.
176 float rate
= params
.rate
> 3 ? 3 : params
.rate
;
177 rate
= params
.rate
< 0.334 ? 0.334 : rate
;
178 float pitch
= params
.pitch
> 3 ? 3 : params
.pitch
;
179 pitch
= params
.pitch
< 0.334 ? 0.334 : pitch
;
181 std::map
<std::string
, SPDChromeVoice
>::iterator it
=
182 all_native_voices_
->find(voice
.name
);
183 if (it
!= all_native_voices_
->end()) {
184 libspeechd_loader_
.spd_set_output_module(conn_
, it
->second
.module
.c_str());
185 libspeechd_loader_
.spd_set_synthesis_voice(conn_
, it
->second
.name
.c_str());
188 // Map our multiplicative range to Speech Dispatcher's linear range.
191 libspeechd_loader_
.spd_set_voice_rate(conn_
, 100 * log10(rate
) / log10(3));
192 libspeechd_loader_
.spd_set_voice_pitch(conn_
, 100 * log10(pitch
) / log10(3));
194 utterance_
= utterance
;
195 utterance_id_
= utterance_id
;
197 if (libspeechd_loader_
.spd_say(conn_
, SPD_TEXT
, utterance
.c_str()) == -1) {
204 bool TtsPlatformImplLinux::StopSpeaking() {
205 if (!PlatformImplAvailable())
207 if (libspeechd_loader_
.spd_stop(conn_
) == -1) {
214 void TtsPlatformImplLinux::Pause() {
215 if (!PlatformImplAvailable())
217 libspeechd_loader_
.spd_pause(conn_
);
220 void TtsPlatformImplLinux::Resume() {
221 if (!PlatformImplAvailable())
223 libspeechd_loader_
.spd_resume(conn_
);
226 bool TtsPlatformImplLinux::IsSpeaking() {
227 return current_notification_
== SPD_EVENT_BEGIN
;
230 void TtsPlatformImplLinux::GetVoices(
231 std::vector
<VoiceData
>* out_voices
) {
232 if (!all_native_voices_
.get()) {
233 all_native_voices_
.reset(new std::map
<std::string
, SPDChromeVoice
>());
234 char** modules
= libspeechd_loader_
.spd_list_modules(conn_
);
237 for (int i
= 0; modules
[i
]; i
++) {
238 char* module
= modules
[i
];
239 libspeechd_loader_
.spd_set_output_module(conn_
, module
);
240 SPDVoice
** native_voices
=
241 libspeechd_loader_
.spd_list_synthesis_voices(conn_
);
242 if (!native_voices
) {
246 for (int j
= 0; native_voices
[j
]; j
++) {
247 SPDVoice
* native_voice
= native_voices
[j
];
248 SPDChromeVoice native_data
;
249 native_data
.name
= native_voice
->name
;
250 native_data
.module
= module
;
252 key
.append(native_data
.name
);
254 key
.append(native_data
.module
);
255 all_native_voices_
->insert(
256 std::pair
<std::string
, SPDChromeVoice
>(key
, native_data
));
257 free(native_voices
[j
]);
263 for (std::map
<std::string
, SPDChromeVoice
>::iterator it
=
264 all_native_voices_
->begin();
265 it
!= all_native_voices_
->end();
267 out_voices
->push_back(VoiceData());
268 VoiceData
& voice
= out_voices
->back();
270 voice
.name
= it
->first
;
271 voice
.events
.insert(TTS_EVENT_START
);
272 voice
.events
.insert(TTS_EVENT_END
);
273 voice
.events
.insert(TTS_EVENT_CANCELLED
);
274 voice
.events
.insert(TTS_EVENT_MARKER
);
275 voice
.events
.insert(TTS_EVENT_PAUSE
);
276 voice
.events
.insert(TTS_EVENT_RESUME
);
280 void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type
) {
281 TtsController
* controller
= TtsController::GetInstance();
283 case SPD_EVENT_BEGIN
:
284 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_START
, 0, std::string());
286 case SPD_EVENT_RESUME
:
287 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_RESUME
, 0, std::string());
290 controller
->OnTtsEvent(
291 utterance_id_
, TTS_EVENT_END
, utterance_
.size(), std::string());
293 case SPD_EVENT_PAUSE
:
294 controller
->OnTtsEvent(
295 utterance_id_
, TTS_EVENT_PAUSE
, utterance_
.size(), std::string());
297 case SPD_EVENT_CANCEL
:
298 controller
->OnTtsEvent(
299 utterance_id_
, TTS_EVENT_CANCELLED
, 0, std::string());
301 case SPD_EVENT_INDEX_MARK
:
302 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_MARKER
, 0, std::string());
308 void TtsPlatformImplLinux::NotificationCallback(
309 size_t msg_id
, size_t client_id
, SPDNotificationType type
) {
310 // We run Speech Dispatcher in threaded mode, so these callbacks should always
311 // be in a separate thread.
312 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
313 current_notification_
= type
;
314 BrowserThread::PostTask(
317 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
318 base::Unretained(TtsPlatformImplLinux::GetInstance()),
324 void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id
,
326 SPDNotificationType state
,
328 // TODO(dtseng): index_mark appears to specify an index type supplied by a
329 // client. Need to explore how this is used before hooking it up with existing
330 // word, sentence events.
331 // We run Speech Dispatcher in threaded mode, so these callbacks should always
332 // be in a separate thread.
333 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
334 current_notification_
= state
;
335 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
336 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
337 base::Unretained(TtsPlatformImplLinux::GetInstance()),
343 TtsPlatformImplLinux
* TtsPlatformImplLinux::GetInstance() {
344 return Singleton
<TtsPlatformImplLinux
,
345 LeakySingletonTraits
<TtsPlatformImplLinux
> >::get();
349 TtsPlatformImpl
* TtsPlatformImpl::GetInstance() {
350 return TtsPlatformImplLinux::GetInstance();