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 base::CommandLine
& command_line
=
98 *base::CommandLine::ForCurrentProcess();
99 if (!command_line
.HasSwitch(switches::kEnableSpeechDispatcher
))
102 BrowserThread::PostTask(BrowserThread::FILE,
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"))
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_SINGLE
);
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_
);
144 libspeechd_loader_
.spd_close(conn_
);
149 void TtsPlatformImplLinux::Reset() {
150 base::AutoLock
lock(initialization_lock_
);
152 libspeechd_loader_
.spd_close(conn_
);
153 conn_
= libspeechd_loader_
.spd_open(
154 "chrome", "extension_api", NULL
, SPD_MODE_SINGLE
);
157 bool TtsPlatformImplLinux::PlatformImplAvailable() {
158 if (!initialization_lock_
.Try())
160 bool result
= libspeechd_loader_
.loaded() && (conn_
!= NULL
);
161 initialization_lock_
.Release();
165 bool TtsPlatformImplLinux::Speak(
167 const std::string
& utterance
,
168 const std::string
& lang
,
169 const VoiceData
& voice
,
170 const UtteranceContinuousParameters
& params
) {
171 if (!PlatformImplAvailable()) {
172 error_
= kNotSupportedError
;
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.
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 utterance_
= utterance
;
196 utterance_id_
= utterance_id
;
198 if (libspeechd_loader_
.spd_say(conn_
, SPD_TEXT
, utterance
.c_str()) == -1) {
205 bool TtsPlatformImplLinux::StopSpeaking() {
206 if (!PlatformImplAvailable())
208 if (libspeechd_loader_
.spd_stop(conn_
) == -1) {
215 void TtsPlatformImplLinux::Pause() {
216 if (!PlatformImplAvailable())
218 libspeechd_loader_
.spd_pause(conn_
);
221 void TtsPlatformImplLinux::Resume() {
222 if (!PlatformImplAvailable())
224 libspeechd_loader_
.spd_resume(conn_
);
227 bool TtsPlatformImplLinux::IsSpeaking() {
228 return current_notification_
== SPD_EVENT_BEGIN
;
231 void TtsPlatformImplLinux::GetVoices(
232 std::vector
<VoiceData
>* out_voices
) {
233 if (!all_native_voices_
.get()) {
234 all_native_voices_
.reset(new std::map
<std::string
, SPDChromeVoice
>());
235 char** modules
= libspeechd_loader_
.spd_list_modules(conn_
);
238 for (int i
= 0; modules
[i
]; i
++) {
239 char* module
= modules
[i
];
240 libspeechd_loader_
.spd_set_output_module(conn_
, module
);
241 SPDVoice
** native_voices
=
242 libspeechd_loader_
.spd_list_synthesis_voices(conn_
);
243 if (!native_voices
) {
247 for (int j
= 0; native_voices
[j
]; j
++) {
248 SPDVoice
* native_voice
= native_voices
[j
];
249 SPDChromeVoice native_data
;
250 native_data
.name
= native_voice
->name
;
251 native_data
.module
= module
;
253 key
.append(native_data
.name
);
255 key
.append(native_data
.module
);
256 all_native_voices_
->insert(
257 std::pair
<std::string
, SPDChromeVoice
>(key
, native_data
));
258 free(native_voices
[j
]);
264 for (std::map
<std::string
, SPDChromeVoice
>::iterator it
=
265 all_native_voices_
->begin();
266 it
!= all_native_voices_
->end();
268 out_voices
->push_back(VoiceData());
269 VoiceData
& voice
= out_voices
->back();
271 voice
.name
= it
->first
;
272 voice
.events
.insert(TTS_EVENT_START
);
273 voice
.events
.insert(TTS_EVENT_END
);
274 voice
.events
.insert(TTS_EVENT_CANCELLED
);
275 voice
.events
.insert(TTS_EVENT_MARKER
);
276 voice
.events
.insert(TTS_EVENT_PAUSE
);
277 voice
.events
.insert(TTS_EVENT_RESUME
);
281 void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type
) {
282 TtsController
* controller
= TtsController::GetInstance();
284 case SPD_EVENT_BEGIN
:
285 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_START
, 0, std::string());
287 case SPD_EVENT_RESUME
:
288 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_RESUME
, 0, std::string());
291 controller
->OnTtsEvent(
292 utterance_id_
, TTS_EVENT_END
, utterance_
.size(), std::string());
294 case SPD_EVENT_PAUSE
:
295 controller
->OnTtsEvent(
296 utterance_id_
, TTS_EVENT_PAUSE
, utterance_
.size(), std::string());
298 case SPD_EVENT_CANCEL
:
299 controller
->OnTtsEvent(
300 utterance_id_
, TTS_EVENT_CANCELLED
, 0, std::string());
302 case SPD_EVENT_INDEX_MARK
:
303 controller
->OnTtsEvent(utterance_id_
, TTS_EVENT_MARKER
, 0, std::string());
309 void TtsPlatformImplLinux::NotificationCallback(
310 size_t msg_id
, size_t client_id
, SPDNotificationType type
) {
311 // We run Speech Dispatcher in threaded mode, so these callbacks should always
312 // be in a separate thread.
313 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
314 current_notification_
= type
;
315 BrowserThread::PostTask(
318 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
319 base::Unretained(TtsPlatformImplLinux::GetInstance()),
325 void TtsPlatformImplLinux::IndexMarkCallback(size_t msg_id
,
327 SPDNotificationType state
,
329 // TODO(dtseng): index_mark appears to specify an index type supplied by a
330 // client. Need to explore how this is used before hooking it up with existing
331 // word, sentence events.
332 // We run Speech Dispatcher in threaded mode, so these callbacks should always
333 // be in a separate thread.
334 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
335 current_notification_
= state
;
336 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
337 base::Bind(&TtsPlatformImplLinux::OnSpeechEvent
,
338 base::Unretained(TtsPlatformImplLinux::GetInstance()),
344 TtsPlatformImplLinux
* TtsPlatformImplLinux::GetInstance() {
345 return Singleton
<TtsPlatformImplLinux
,
346 LeakySingletonTraits
<TtsPlatformImplLinux
> >::get();
350 TtsPlatformImpl
* TtsPlatformImpl::GetInstance() {
351 return TtsPlatformImplLinux::GetInstance();