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/debug/leak_annotations.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/memory/singleton.h"
12 #include "base/synchronization/lock.h"
13 #include "chrome/browser/speech/tts_platform.h"
14 #include "content/public/browser/browser_thread.h"
16 #include "library_loaders/libspeechd.h"
18 using content::BrowserThread
;
22 const char kNotSupportedError
[] =
23 "Native speech synthesis not supported on this platform.";
25 struct SPDChromeVoice
{
32 class TtsPlatformImplLinux
: public TtsPlatformImpl
{
34 virtual bool PlatformImplAvailable() OVERRIDE
;
37 const std::string
& utterance
,
38 const std::string
& lang
,
39 const VoiceData
& voice
,
40 const UtteranceContinuousParameters
& params
) OVERRIDE
;
41 virtual bool StopSpeaking() OVERRIDE
;
42 virtual void Pause() OVERRIDE
;
43 virtual void Resume() OVERRIDE
;
44 virtual bool IsSpeaking() OVERRIDE
;
45 virtual void GetVoices(std::vector
<VoiceData
>* out_voices
) OVERRIDE
;
47 void OnSpeechEvent(SPDNotificationType type
);
49 // Get the single instance of this class.
50 static TtsPlatformImplLinux
* GetInstance();
53 TtsPlatformImplLinux();
54 virtual ~TtsPlatformImplLinux();
56 // Initiate the connection with the speech dispatcher.
59 // Resets the connection with speech dispatcher.
62 static void NotificationCallback(size_t msg_id
,
64 SPDNotificationType type
);
66 static void IndexMarkCallback(size_t msg_id
,
68 SPDNotificationType state
,
71 static SPDNotificationType current_notification_
;
73 base::Lock initialization_lock_
;
74 LibSpeechdLoader libspeechd_loader_
;
77 // These apply to the current utterance only.
78 std::string utterance_
;
81 // Map a string composed of a voicename and module to the voicename. Used to
82 // uniquely identify a voice across all available modules.
83 scoped_ptr
<std::map
<std::string
, SPDChromeVoice
> > all_native_voices_
;
85 friend struct DefaultSingletonTraits
<TtsPlatformImplLinux
>;
87 DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplLinux
);
91 SPDNotificationType
TtsPlatformImplLinux::current_notification_
=
94 TtsPlatformImplLinux::TtsPlatformImplLinux()
96 BrowserThread::PostTask(BrowserThread::FILE,
98 base::Bind(&TtsPlatformImplLinux::Initialize
,
99 base::Unretained(this)));
102 void TtsPlatformImplLinux::Initialize() {
103 base::AutoLock
lock(initialization_lock_
);
105 if (!libspeechd_loader_
.Load("libspeechd.so.2"))
109 // spd_open has memory leaks which are hard to suppress.
110 // http://crbug.com/317360
111 ANNOTATE_SCOPED_MEMORY_LEAK
;
112 conn_
= libspeechd_loader_
.spd_open(
113 "chrome", "extension_api", NULL
, SPD_MODE_SINGLE
);
118 // Register callbacks for all events.
119 conn_
->callback_begin
=
120 conn_
->callback_end
=
121 conn_
->callback_cancel
=
122 conn_
->callback_pause
=
123 conn_
->callback_resume
=
124 &NotificationCallback
;
126 conn_
->callback_im
= &IndexMarkCallback
;
128 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_BEGIN
);
129 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_END
);
130 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_CANCEL
);
131 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_PAUSE
);
132 libspeechd_loader_
.spd_set_notification_on(conn_
, SPD_RESUME
);
135 TtsPlatformImplLinux::~TtsPlatformImplLinux() {
136 base::AutoLock
lock(initialization_lock_
);
138 libspeechd_loader_
.spd_close(conn_
);
143 void TtsPlatformImplLinux::Reset() {
144 base::AutoLock
lock(initialization_lock_
);
146 libspeechd_loader_
.spd_close(conn_
);
147 conn_
= libspeechd_loader_
.spd_open(
148 "chrome", "extension_api", NULL
, SPD_MODE_SINGLE
);
151 bool TtsPlatformImplLinux::PlatformImplAvailable() {
152 if (!initialization_lock_
.Try())
154 bool result
= libspeechd_loader_
.loaded() && (conn_
!= NULL
);
155 initialization_lock_
.Release();
159 bool TtsPlatformImplLinux::Speak(
161 const std::string
& utterance
,
162 const std::string
& lang
,
163 const VoiceData
& voice
,
164 const UtteranceContinuousParameters
& params
) {
165 if (!PlatformImplAvailable()) {
166 error_
= kNotSupportedError
;
170 // Speech dispatcher's speech params are around 3x at either limit.
171 float rate
= params
.rate
> 3 ? 3 : params
.rate
;
172 rate
= params
.rate
< 0.334 ? 0.334 : rate
;
173 float pitch
= params
.pitch
> 3 ? 3 : params
.pitch
;
174 pitch
= params
.pitch
< 0.334 ? 0.334 : pitch
;
176 std::map
<std::string
, SPDChromeVoice
>::iterator it
=
177 all_native_voices_
->find(voice
.name
);
178 if (it
!= all_native_voices_
->end()) {
179 libspeechd_loader_
.spd_set_output_module(conn_
, it
->second
.module
.c_str());
180 libspeechd_loader_
.spd_set_synthesis_voice(conn_
, it
->second
.name
.c_str());
183 // Map our multiplicative range to Speech Dispatcher's linear range.
186 libspeechd_loader_
.spd_set_voice_rate(conn_
, 100 * log10(rate
) / log10(3));
187 libspeechd_loader_
.spd_set_voice_pitch(conn_
, 100 * log10(pitch
) / log10(3));
189 utterance_
= utterance
;
190 utterance_id_
= utterance_id
;
192 if (libspeechd_loader_
.spd_say(conn_
, SPD_TEXT
, utterance
.c_str()) == -1) {
199 bool TtsPlatformImplLinux::StopSpeaking() {
200 if (!PlatformImplAvailable())
202 if (libspeechd_loader_
.spd_stop(conn_
) == -1) {
209 void TtsPlatformImplLinux::Pause() {
210 if (!PlatformImplAvailable())
212 libspeechd_loader_
.spd_pause(conn_
);
215 void TtsPlatformImplLinux::Resume() {
216 if (!PlatformImplAvailable())
218 libspeechd_loader_
.spd_resume(conn_
);
221 bool TtsPlatformImplLinux::IsSpeaking() {
222 return current_notification_
== SPD_EVENT_BEGIN
;
225 void TtsPlatformImplLinux::GetVoices(
226 std::vector
<VoiceData
>* out_voices
) {
227 if (!all_native_voices_
.get()) {
228 all_native_voices_
.reset(new std::map
<std::string
, SPDChromeVoice
>());
229 char** modules
= libspeechd_loader_
.spd_list_modules(conn_
);
232 for (int i
= 0; modules
[i
]; i
++) {
233 char* module
= modules
[i
];
234 libspeechd_loader_
.spd_set_output_module(conn_
, module
);
235 SPDVoice
** native_voices
=
236 libspeechd_loader_
.spd_list_synthesis_voices(conn_
);
237 if (!native_voices
) {
241 for (int j
= 0; native_voices
[j
]; j
++) {
242 SPDVoice
* native_voice
= native_voices
[j
];
243 SPDChromeVoice native_data
;
244 native_data
.name
= native_voice
->name
;
245 native_data
.module
= module
;
247 key
.append(native_data
.name
);
249 key
.append(native_data
.module
);
250 all_native_voices_
->insert(
251 std::pair
<std::string
, SPDChromeVoice
>(key
, native_data
));
252 free(native_voices
[j
]);
258 for (std::map
<std::string
, SPDChromeVoice
>::iterator it
=
259 all_native_voices_
->begin();
260 it
!= all_native_voices_
->end();
262 out_voices
->push_back(VoiceData());
263 VoiceData
& voice
= out_voices
->back();
265 voice
.name
= it
->first
;
266 voice
.events
.insert(TTS_EVENT_START
);
267 voice
.events
.insert(TTS_EVENT_END
);
268 voice
.events
.insert(TTS_EVENT_CANCELLED
);
269 voice
.events
.insert(TTS_EVENT_MARKER
);
270 voice
.events
.insert(TTS_EVENT_PAUSE
);
271 voice
.events
.insert(TTS_EVENT_RESUME
);
275 void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type
) {
276 TtsController
* controller
= TtsController::GetInstance();
278 case SPD_EVENT_BEGIN
:
279 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_START
, 0, std::string());
281 case SPD_EVENT_RESUME
:
282 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_RESUME
, 0, std::string());
285 controller
->OnTtsEvent(
286 utterance_id_
, TTS_EVENT_END
, utterance_
.size(), std::string());
288 case SPD_EVENT_PAUSE
:
289 controller
->OnTtsEvent(
290 utterance_id_
, TTS_EVENT_PAUSE
, utterance_
.size(), std::string());
292 case SPD_EVENT_CANCEL
:
293 controller
->OnTtsEvent(
294 utterance_id_
, TTS_EVENT_CANCELLED
, 0, std::string());
296 case SPD_EVENT_INDEX_MARK
:
297 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_MARKER
, 0, std::string());
303 void TtsPlatformImplLinux::NotificationCallback(
304 size_t msg_id
, size_t client_id
, SPDNotificationType type
) {
305 // We run Speech Dispatcher in threaded mode, so these callbacks should always
306 // be in a separate thread.
307 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
308 current_notification_
= type
;
309 BrowserThread::PostTask(
312 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
313 base::Unretained(TtsPlatformImplLinux::GetInstance()),
319 void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id
,
321 SPDNotificationType state
,
323 // TODO(dtseng): index_mark appears to specify an index type supplied by a
324 // client. Need to explore how this is used before hooking it up with existing
325 // word, sentence events.
326 // We run Speech Dispatcher in threaded mode, so these callbacks should always
327 // be in a separate thread.
328 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
329 current_notification_
= state
;
330 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
331 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
332 base::Unretained(TtsPlatformImplLinux::GetInstance()),
338 TtsPlatformImplLinux
* TtsPlatformImplLinux::GetInstance() {
339 return Singleton
<TtsPlatformImplLinux
,
340 LeakySingletonTraits
<TtsPlatformImplLinux
> >::get();
344 TtsPlatformImpl
* TtsPlatformImpl::GetInstance() {
345 return TtsPlatformImplLinux::GetInstance();