MPEGPlayer: Skip to next file when there is a problem with a video file in all-play...
[kugel-rb.git] / rbutil / rbutilqt / base / ttscarbon.cpp
blob63fb5315e393ea034c828b53ff5187161ed0a41f
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2010 by Dominik Riebeling
10 * $Id$
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
20 #include <QtCore>
21 #include "ttsbase.h"
22 #include "ttscarbon.h"
23 #include "encttssettings.h"
24 #include "rbsettings.h"
26 #include <CoreFoundation/CoreFoundation.h>
27 #include <ApplicationServices/ApplicationServices.h>
28 #include <Carbon/Carbon.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31 #include <inttypes.h>
33 TTSCarbon::TTSCarbon(QObject* parent) : TTSBase(parent)
37 TTSBase::Capabilities TTSCarbon::capabilities()
39 return None;
42 bool TTSCarbon::configOk()
44 return true;
48 bool TTSCarbon::start(QString *errStr)
50 (void)errStr;
51 VoiceSpec vspec;
52 VoiceSpec* vspecref = NULL;
53 VoiceDescription vdesc;
54 OSErr error;
55 QString selectedVoice
56 = RbSettings::subValue("carbon", RbSettings::TtsVoice).toString();
57 SInt16 numVoices;
58 SInt16 voiceIndex;
59 error = CountVoices(&numVoices);
60 for(voiceIndex = 1; voiceIndex < numVoices; ++voiceIndex) {
61 error = GetIndVoice(voiceIndex, &vspec);
62 error = GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc));
63 // name is pascal string, i.e. the first byte is the length.
64 QString name = QString::fromLocal8Bit((const char*)&vdesc.name[1],
65 vdesc.name[0]);
66 if(name == selectedVoice) {
67 vspecref = &vspec;
68 if(vdesc.script != -1)
69 m_voiceScript = (CFStringBuiltInEncodings)vdesc.script;
70 else
71 m_voiceScript = (CFStringBuiltInEncodings)vdesc.reserved[0];
72 break;
75 if(voiceIndex == numVoices) {
76 // voice not found. Add user notification here and proceed with
77 // system default voice.
78 qDebug() << "selected voice not found, using system default!";
79 GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc));
80 if(vdesc.script != -1)
81 m_voiceScript = (CFStringBuiltInEncodings)vdesc.script;
82 else
83 m_voiceScript = (CFStringBuiltInEncodings)vdesc.reserved[0];
86 error = NewSpeechChannel(vspecref, &m_channel);
87 //SetSpeechInfo(channel, soSpeechDoneCallBack, speechDone);
88 Fixed rate = (Fixed)(0x10000 * RbSettings::subValue("carbon",
89 RbSettings::TtsSpeed).toInt());
90 if(rate != 0)
91 SetSpeechRate(m_channel, rate);
92 return (error == 0) ? true : false;
96 bool TTSCarbon::stop(void)
98 DisposeSpeechChannel(m_channel);
99 return true;
103 void TTSCarbon::generateSettings(void)
105 QStringList voiceNames;
106 QString systemVoice;
107 SInt16 numVoices;
108 OSErr error;
109 VoiceSpec vspec;
110 VoiceDescription vdesc;
112 // get system voice
113 error = GetVoiceDescription(NULL, &vdesc, sizeof(vdesc));
114 systemVoice
115 = QString::fromLocal8Bit((const char*)&vdesc.name[1], vdesc.name[0]);
116 // get list of all voices
117 CountVoices(&numVoices);
118 for(SInt16 i = 1; i < numVoices; ++i) {
119 error = GetIndVoice(i, &vspec);
120 error = GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc));
121 // name is pascal string, i.e. the first byte is the length.
122 QString name
123 = QString::fromLocal8Bit((const char*)&vdesc.name[1], vdesc.name[0]);
124 voiceNames.append(name.trimmed());
126 // voice
127 EncTtsSetting* setting;
128 QString voice
129 = RbSettings::subValue("carbon", RbSettings::TtsVoice).toString();
130 if(voice.isEmpty())
131 voice = systemVoice;
132 setting = new EncTtsSetting(this, EncTtsSetting::eSTRINGLIST,
133 tr("Voice:"), voice, voiceNames, EncTtsSetting::eNOBTN);
134 insertSetting(ConfigVoice, setting);
136 // speed
137 int speed = RbSettings::subValue("carbon", RbSettings::TtsSpeed).toInt();
138 setting = new EncTtsSetting(this, EncTtsSetting::eINT,
139 tr("Speed (words/min):"), speed, 80, 500,
140 EncTtsSetting::eNOBTN);
141 insertSetting(ConfigSpeed, setting);
145 void TTSCarbon::saveSettings(void)
147 // save settings in user config
148 RbSettings::setSubValue("carbon", RbSettings::TtsVoice,
149 getSetting(ConfigVoice)->current().toString());
150 RbSettings::setSubValue("carbon", RbSettings::TtsSpeed,
151 getSetting(ConfigSpeed)->current().toInt());
152 RbSettings::sync();
156 /** @brief create wav file from text using the selected TTS voice.
158 TTSStatus TTSCarbon::voice(QString text, QString wavfile, QString* errStr)
160 TTSStatus status = NoError;
161 OSErr error;
163 QString aifffile = wavfile + ".aiff";
164 // FIXME: find out why we need to do this.
165 // Create a local copy of the temporary file filename.
166 // Not doing so causes weird issues (path contains trailing spaces)
167 unsigned int len = aifffile.size() + 1;
168 char* tmpfile = (char*)malloc(len * sizeof(char));
169 strncpy(tmpfile, aifffile.toLocal8Bit().constData(), len);
170 CFStringRef tmpfileref = CFStringCreateWithCString(kCFAllocatorDefault,
171 tmpfile, kCFStringEncodingUTF8);
172 CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
173 tmpfileref, kCFURLPOSIXPathStyle, false);
174 SetSpeechInfo(m_channel, soOutputToFileWithCFURL, urlref);
176 // speak it.
177 // Convert the string to the encoding requested by the voice. Do this
178 // via CFString, as this allows to directly use the destination encoding
179 // as CFString uses the same values as the voice.
181 // allocate enough space to allow storing the string in a 2 byte encoding
182 unsigned int textlen = 2 * text.length() + 1;
183 char* textbuf = (char*)calloc(textlen, sizeof(char));
184 char* utf8data = (char*)text.toUtf8().constData();
185 int utf8bytes = text.toUtf8().size();
186 CFStringRef cfstring = CFStringCreateWithBytes(kCFAllocatorDefault,
187 (UInt8*)utf8data, utf8bytes,
188 kCFStringEncodingUTF8, (Boolean)false);
189 CFIndex usedBuf = 0;
190 CFRange range;
191 range.location = 0; // character in string to start.
192 range.length = text.length(); // number of _characters_ in string
193 // FIXME: check if converting between encodings was lossless.
194 CFStringGetBytes(cfstring, range, m_voiceScript, ' ',
195 false, (UInt8*)textbuf, textlen, &usedBuf);
197 error = SpeakText(m_channel, textbuf, (unsigned long)usedBuf);
198 while(SpeechBusy()) {
199 // FIXME: add small delay here to make calls less frequent
200 QCoreApplication::processEvents();
202 if(error != 0) {
203 *errStr = tr("Could not voice string");
204 status = FatalError;
206 free(textbuf);
207 CFRelease(cfstring);
209 // convert the temporary aiff file to wav
210 if(status == NoError
211 && convertAiffToWav(tmpfile, wavfile.toLocal8Bit().constData()) != 0) {
212 *errStr = tr("Could not convert intermediate file");
213 status = FatalError;
215 // remove temporary aiff file
216 unlink(tmpfile);
217 free(tmpfile);
219 return status;
223 unsigned long TTSCarbon::be2u32(unsigned char* buf)
225 return (buf[0]&0xff)<<24 | (buf[1]&0xff)<<16 | (buf[2]&0xff)<<8 | (buf[3]&0xff);
229 unsigned long TTSCarbon::be2u16(unsigned char* buf)
231 return buf[1]&0xff | (buf[0]&0xff)<<8;
235 unsigned char* TTSCarbon::u32tobuf(unsigned char* buf, uint32_t val)
237 buf[0] = val & 0xff;
238 buf[1] = (val>> 8) & 0xff;
239 buf[2] = (val>>16) & 0xff;
240 buf[3] = (val>>24) & 0xff;
241 return buf;
245 unsigned char* TTSCarbon::u16tobuf(unsigned char* buf, uint16_t val)
247 buf[0] = val & 0xff;
248 buf[1] = (val>> 8) & 0xff;
249 return buf;
253 /** @brief convert 80 bit extended ("long double") to int.
254 * This is simplified to handle the usual audio sample rates. Everything else
255 * might break. If the value isn't supported it will return 0.
256 * Conversion taken from Rockbox aiff codec.
258 unsigned int TTSCarbon::extended2int(unsigned char* buf)
260 unsigned int result = 0;
261 /* value negative? */
262 if(buf[0] & 0x80)
263 return 0;
264 /* check exponent. Int can handle up to 2^31. */
265 int exponent = buf[0] << 8 | buf[1];
266 if(exponent < 0x4000 || exponent > (0x4000 + 30))
267 return 0;
268 result = ((buf[2]<<24) | (buf[3]<<16) | (buf[4]<<8) | buf[5]) + 1;
269 result >>= (16 + 14 - buf[1]);
270 return result;
274 /** @brief Convert aiff file to wav. Returns 0 on success.
276 int TTSCarbon::convertAiffToWav(const char* aiff, const char* wav)
278 struct commchunk {
279 unsigned long chunksize;
280 unsigned short channels;
281 unsigned long frames;
282 unsigned short size;
283 int rate;
286 struct ssndchunk {
287 unsigned long chunksize;
288 unsigned long offset;
289 unsigned long blocksize;
292 FILE* in;
293 FILE* out;
294 unsigned char obuf[4];
295 unsigned char* buf;
296 /* minimum file size for a valid aiff file is 46 bytes:
297 * - FORM chunk: 12 bytes
298 * - COMM chunk: 18 bytes
299 * - SSND chunk: 16 bytes (with no actual data)
301 struct stat filestat;
302 stat(aiff, &filestat);
303 if(filestat.st_size < 46)
304 return -1;
305 /* read input file into memory */
306 buf = (unsigned char*)malloc(filestat.st_size * sizeof(unsigned char));
307 if(!buf) /* error out if malloc() failed */
308 return -1;
309 in = fopen(aiff, "rb");
310 if(fread(buf, 1, filestat.st_size, in) < filestat.st_size) {
311 printf("could not read file: not enought bytes read\n");
312 fclose(in);
313 return -1;
315 fclose(in);
317 /* check input file format */
318 if(memcmp(buf, "FORM", 4) | memcmp(&buf[8], "AIFF", 4)) {
319 printf("No valid AIFF header found.\n");
320 free(buf);
321 return -1;
323 /* read COMM chunk */
324 unsigned char* commstart = &buf[12];
325 struct commchunk comm;
326 if(memcmp(commstart, "COMM", 4)) {
327 printf("COMM chunk not at beginning.\n");
328 free(buf);
329 return -1;
331 comm.chunksize = be2u32(&commstart[4]);
332 comm.channels = be2u16(&commstart[8]);
333 comm.frames = be2u32(&commstart[10]);
334 comm.size = be2u16(&commstart[14]);
335 comm.rate = extended2int(&commstart[16]);
337 /* find SSND as next chunk */
338 unsigned char* ssndstart = commstart + 8 + comm.chunksize;
339 while(memcmp(ssndstart, "SSND", 4) && ssndstart < (buf + filestat.st_size)) {
340 printf("Skipping chunk.\n");
341 ssndstart += be2u32(&ssndstart[4]) + 8;
343 if(ssndstart > (buf + filestat.st_size)) {
344 free(buf);
345 return -1;
348 struct ssndchunk ssnd;
349 ssnd.chunksize = be2u32(&ssndstart[4]);
350 ssnd.offset = be2u32(&ssndstart[8]);
351 ssnd.blocksize = be2u32(&ssndstart[12]);
353 /* Calculate the total length of the resulting RIFF chunk.
354 * The length is given by frames * samples * bytes/sample.
355 * We need to add:
356 * - 16 bytes: fmt chunk header
357 * - 8 bytes: data chunk header
358 * - 4 bytes: wave chunk identifier
360 out = fopen(wav, "wb+");
362 /* write the wav header */
363 unsigned short blocksize = comm.channels * (comm.size >> 3);
364 unsigned long rifflen = blocksize * comm.frames + 28;
365 fwrite("RIFF", 1, 4, out);
366 fwrite(u32tobuf(obuf, rifflen), 1, 4, out);
367 fwrite("WAVE", 1, 4, out);
369 /* write the fmt chunk and chunk size (always 16) */
370 /* write fmt chunk header:
371 * header, size (always 0x10, format code (always 0x0001)
373 fwrite("fmt \x10\x00\x00\x00\x01\x00", 1, 10, out);
374 /* number of channels (2 bytes) */
375 fwrite(u16tobuf(obuf, comm.channels), 1, 2, out);
376 /* sampling rate (4 bytes) */
377 fwrite(u32tobuf(obuf, comm.rate), 1, 4, out);
379 /* data rate, i.e. bytes/sec */
380 fwrite(u32tobuf(obuf, comm.rate * blocksize), 1, 4, out);
382 /* data block size */
383 fwrite(u16tobuf(obuf, blocksize), 1, 2, out);
385 /* bits per sample */
386 fwrite(u16tobuf(obuf, comm.size), 1, 2, out);
388 /* write the data chunk */
389 /* chunk id */
390 fwrite("data", 1, 4, out);
391 /* chunk size: 4 bytes. */
392 unsigned long cs = blocksize * comm.frames;
393 fwrite(u32tobuf(obuf, cs), 1, 4, out);
395 /* write data */
396 unsigned char* data = ssndstart;
397 unsigned long pos = ssnd.chunksize;
398 /* byteswap if samples are 16 bit */
399 if(comm.size == 16) {
400 while(pos) {
401 obuf[1] = *data++ & 0xff;
402 obuf[0] = *data++ & 0xff;
403 fwrite(obuf, 1, 2, out);
404 pos -= 2;
407 /* 8 bit samples have need no conversion so we can bulk copy.
408 * Everything that is not 16 bit is considered 8. */
409 else {
410 fwrite(data, 1, pos, out);
412 /* number of bytes has to be even, even if chunksize is not. */
413 if(cs % 2) {
414 fwrite(obuf, 1, 1, out);
417 fclose(out);
418 free(buf);
419 return 0;