summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorJörg Hohensohn <hohensoh@rockbox.org>2004-05-09 09:41:23 +0000
committerJörg Hohensohn <hohensoh@rockbox.org>2004-05-09 09:41:23 +0000
commit2fef5b7b4a3ab4224de48a5c32b6fb1ae27b5266 (patch)
tree0c6362816f0bf379253b47ce8a589d15325cf3a9 /apps
parent30c338a4c156bbe7b26fe7942dbd789518fd7818 (diff)
downloadrockbox-2fef5b7b4a3ab4224de48a5c32b6fb1ae27b5266.tar.gz
rockbox-2fef5b7b4a3ab4224de48a5c32b6fb1ae27b5266.zip
While searching the voice crash like a madman, I made this module a lot more safe, so I can as well commit that.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@4598 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps')
-rw-r--r--apps/talk.c202
1 files changed, 110 insertions, 92 deletions
diff --git a/apps/talk.c b/apps/talk.c
index df5c6ef438..2fe3536ead 100644
--- a/apps/talk.c
+++ b/apps/talk.c
@@ -11,7 +11,7 @@
11 * 11 *
12 * This module collects the Talkbox and voice UI functions. 12 * This module collects the Talkbox and voice UI functions.
13 * (Talkbox reads directory names from mp3 clips called thumbnails, 13 * (Talkbox reads directory names from mp3 clips called thumbnails,
14 * the voice UI lets menus and screens "talk" from a voicefont in memory. 14 * the voice UI lets menus and screens "talk" from a voicefile in memory.
15 * 15 *
16 * All files in this archive are subject to the GNU General Public License. 16 * All files in this archive are subject to the GNU General Public License.
17 * See the file COPYING in the source tree root for full license agreement. 17 * See the file COPYING in the source tree root for full license agreement.
@@ -36,21 +36,26 @@ extern void bitswap(unsigned char *data, int length); /* no header for this */
36 36
37/***************** Constants *****************/ 37/***************** Constants *****************/
38 38
39#define QUEUE_SIZE 50 39#define QUEUE_SIZE 64 /* must be a power of two */
40#define QUEUE_MASK (QUEUE_SIZE-1)
40const char* dir_thumbnail_name = ".dirname.tbx"; 41const char* dir_thumbnail_name = ".dirname.tbx";
41 42
43/***************** Functional Macros *****************/
44
45#define QUEUE_LEVEL ((queue_write - queue_read) & QUEUE_MASK)
46
42 47
43/***************** Data types *****************/ 48/***************** Data types *****************/
44 49
45struct clip_entry /* one entry of the index table */ 50struct clip_entry /* one entry of the index table */
46{ 51{
47 int offset; /* offset from start of voicefont file */ 52 int offset; /* offset from start of voicefile file */
48 int size; /* size of the clip */ 53 int size; /* size of the clip */
49}; 54};
50 55
51struct voicefont /* file format of our "voicefont" */ 56struct voicefile /* file format of our voice file */
52{ 57{
53 int version; /* version of the voicefont */ 58 int version; /* version of the voicefile */
54 int table; /* offset to index table, (=header size) */ 59 int table; /* offset to index table, (=header size) */
55 int id1_max; /* number of "normal" clips contained in above index */ 60 int id1_max; /* number of "normal" clips contained in above index */
56 int id2_max; /* number of "voice only" clips contained in above index */ 61 int id2_max; /* number of "voice only" clips contained in above index */
@@ -58,7 +63,6 @@ struct voicefont /* file format of our "voicefont" */
58 /* and finally the bitswapped mp3 clips, not visible here */ 63 /* and finally the bitswapped mp3 clips, not visible here */
59}; 64};
60 65
61
62struct queue_entry /* one entry of the internal queue */ 66struct queue_entry /* one entry of the internal queue */
63{ 67{
64 unsigned char* buf; 68 unsigned char* buf;
@@ -66,23 +70,22 @@ struct queue_entry /* one entry of the internal queue */
66}; 70};
67 71
68 72
69
70/***************** Globals *****************/ 73/***************** Globals *****************/
71 74
72static unsigned char* p_thumbnail; /* buffer for thumbnail */ 75static unsigned char* p_thumbnail; /* buffer for thumbnail */
73static long size_for_thumbnail; /* leftover buffer size for it */ 76static long size_for_thumbnail; /* leftover buffer size for it */
74static struct voicefont* p_voicefont; /* loaded voicefont */ 77static struct voicefile* p_voicefile; /* loaded voicefile */
75static bool has_voicefont; /* a voicefont file is present */ 78static bool has_voicefile; /* a voicefile file is present */
76static bool is_playing; /* we're currently playing */
77static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ 79static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */
78static int queue_write; /* write index of queue, by application */ 80static int queue_write; /* write index of queue, by application */
79static int queue_read; /* read index of queue, by ISR context */ 81static int queue_read; /* read index of queue, by ISR context */
82static int sent; /* how many bytes handed over to playback, owned by ISR */
80static unsigned char curr_hd[3]; /* current frame header, for re-sync */ 83static unsigned char curr_hd[3]; /* current frame header, for re-sync */
81 84
82 85
83/***************** Private prototypes *****************/ 86/***************** Private prototypes *****************/
84 87
85static int load_voicefont(void); 88static int load_voicefile(void);
86static void mp3_callback(unsigned char** start, int* size); 89static void mp3_callback(unsigned char** start, int* size);
87static int shutup(void); 90static int shutup(void);
88static int queue_clip(unsigned char* buf, int size, bool enqueue); 91static int queue_clip(unsigned char* buf, int size, bool enqueue);
@@ -108,28 +111,29 @@ static int open_voicefile(void)
108} 111}
109 112
110 113
111 114/* load the voice file into the mp3 buffer */
112static int load_voicefont(void) 115static int load_voicefile(void)
113{ 116{
114 int fd; 117 int fd;
115 int size; 118 int size;
116 119
117 p_voicefont = NULL; /* indicate no voicefont if we fail below */ 120 p_voicefile = NULL; /* indicate no voicefile if we fail below */
118 121
119 fd = open_voicefile(); 122 fd = open_voicefile();
120 if (fd < 0) /* failed to open */ 123 if (fd < 0) /* failed to open */
121 { 124 {
122 p_voicefont = NULL; /* indicate no voicefont */ 125 p_voicefile = NULL; /* indicate no voicefile */
123 has_voicefont = false; /* don't try again */ 126 has_voicefile = false; /* don't try again */
124 return 0; 127 return 0;
125 } 128 }
126 129
127 size = read(fd, mp3buf, mp3end - mp3buf); 130 size = read(fd, mp3buf, mp3end - mp3buf);
128 if (size > 1000 131 if (size > 10000 /* too small is probably invalid */
129 && ((struct voicefont*)mp3buf)->table 132 && size == filesize(fd) /* has to fit completely */
130 == offsetof(struct voicefont, index)) 133 && ((struct voicefile*)mp3buf)->table /* format check */
134 == offsetof(struct voicefile, index))
131 { 135 {
132 p_voicefont = (struct voicefont*)mp3buf; 136 p_voicefile = (struct voicefile*)mp3buf;
133 137
134 /* thumbnail buffer is the remaining space behind */ 138 /* thumbnail buffer is the remaining space behind */
135 p_thumbnail = mp3buf + size; 139 p_thumbnail = mp3buf + size;
@@ -138,7 +142,7 @@ static int load_voicefont(void)
138 } 142 }
139 else 143 else
140 { 144 {
141 has_voicefont = false; /* don't try again */ 145 has_voicefile = false; /* don't try again */
142 } 146 }
143 close(fd); 147 close(fd);
144 148
@@ -149,15 +153,14 @@ static int load_voicefont(void)
149/* called in ISR context if mp3 data got consumed */ 153/* called in ISR context if mp3 data got consumed */
150static void mp3_callback(unsigned char** start, int* size) 154static void mp3_callback(unsigned char** start, int* size)
151{ 155{
152 int play_now; 156 queue[queue_read].len -= sent; /* we completed this */
157 queue[queue_read].buf += sent;
153 158
154 if (queue[queue_read].len > 0) /* current clip not finished? */ 159 if (queue[queue_read].len > 0) /* current clip not finished? */
155 { /* feed the next 64K-1 chunk */ 160 { /* feed the next 64K-1 chunk */
156 play_now = MIN(queue[queue_read].len, 0xFFFF); 161 sent = MIN(queue[queue_read].len, 0xFFFF);
157 *start = queue[queue_read].buf; 162 *start = queue[queue_read].buf;
158 *size = play_now; 163 *size = sent;
159 queue[queue_read].buf += play_now;
160 queue[queue_read].len -= play_now;
161 return; 164 return;
162 } 165 }
163 else /* go to next entry */ 166 else /* go to next entry */
@@ -167,21 +170,18 @@ static void mp3_callback(unsigned char** start, int* size)
167 queue_read = 0; 170 queue_read = 0;
168 } 171 }
169 172
170 if (queue_read != queue_write) /* queue is not empty? */ 173 if (QUEUE_LEVEL) /* queue is not empty? */
171 { /* start next clip */ 174 { /* start next clip */
172 play_now = MIN(queue[queue_read].len, 0xFFFF); 175 sent = MIN(queue[queue_read].len, 0xFFFF);
173 *start = queue[queue_read].buf; 176 *start = queue[queue_read].buf;
174 *size = play_now; 177 *size = sent;
175 curr_hd[0] = *start[1]; 178 curr_hd[0] = *start[1];
176 curr_hd[1] = *start[2]; 179 curr_hd[1] = *start[2];
177 curr_hd[2] = *start[3]; 180 curr_hd[2] = *start[3];
178 queue[queue_read].buf += play_now;
179 queue[queue_read].len -= play_now;
180 } 181 }
181 else 182 else
182 { 183 {
183 *size = 0; /* end of data */ 184 *size = 0; /* end of data */
184 is_playing = false;
185 mp3_play_stop(); /* fixme: should be done by caller */ 185 mp3_play_stop(); /* fixme: should be done by caller */
186 } 186 }
187} 187}
@@ -193,49 +193,55 @@ static int shutup(void)
193 unsigned char* search; 193 unsigned char* search;
194 unsigned char* end; 194 unsigned char* end;
195 195
196 if (!is_playing) /* has ended anyway */ 196 if (QUEUE_LEVEL == 0) /* has ended anyway */
197 {
197 return 0; 198 return 0;
199 }
198 200
199 CHCR3 &= ~0x0001; /* disable the DMA */ 201 CHCR3 &= ~0x0001; /* disable the DMA (and therefore the interrupt also) */
200 202
201 /* search next frame boundary and continue up to there */ 203 /* search next frame boundary and continue up to there */
202 pos = search = mp3_get_pos(); 204 pos = search = mp3_get_pos();
203 end = queue[queue_read].buf + queue[queue_read].len; 205 end = queue[queue_read].buf + queue[queue_read].len;
204 206
205 /* Find the next frame boundary */ 207 if (pos >= queue[queue_read].buf
206 while (search < end) /* search the remaining data */ 208 && pos <= end) /* really our clip? */
207 { 209 { /* (for strange reasons this isn't nesessarily the case) */
208 if (*search++ != 0xFF) /* quick search for frame sync byte */ 210 /* find the next frame boundary */
209 continue; /* (this does the majority of the job) */ 211 while (search < end) /* search the remaining data */
210
211 /* look at the (bitswapped) rest of header candidate */
212 if (search[0] == curr_hd[0] /* do the quicker checks first */
213 && search[2] == curr_hd[2]
214 && (search[1] & 0x30) == (curr_hd[1] & 0x30)) /* sample rate */
215 { 212 {
216 search--; /* back to the sync byte */ 213 if (*search++ != 0xFF) /* quick search for frame sync byte */
217 break; /* From looking at it, this is our header. */ 214 continue; /* (this does the majority of the job) */
215
216 /* look at the (bitswapped) rest of header candidate */
217 if (search[0] == curr_hd[0] /* do the quicker checks first */
218 && search[2] == curr_hd[2]
219 && (search[1] & 0x30) == (curr_hd[1] & 0x30)) /* sample rate */
220 {
221 search--; /* back to the sync byte */
222 break; /* From looking at it, this is our header. */
223 }
218 } 224 }
219 }
220 225
221 if (search-pos) 226 if (search-pos)
222 { /* play old data until the frame end, to keep the MAS in sync */ 227 { /* play old data until the frame end, to keep the MAS in sync */
223 DTCR3 = search-pos; 228 sent = search-pos;
224 229
225 queue_write = queue_read + 1; /* will be empty after next callback */ 230 queue_write = queue_read + 1; /* will be empty after next callback */
226 if (queue_write >= QUEUE_SIZE) 231 if (queue_write >= QUEUE_SIZE)
227 queue_write = 0; 232 queue_write = 0;
228 queue[queue_read].len = 0; /* current one ends now */ 233 queue[queue_read].len = sent; /* current one ends after this */
229 234
230 CHCR3 |= 0x0001; /* re-enable DMA */ 235 DTCR3 = sent; /* let the DMA finish this frame */
231 } 236 CHCR3 |= 0x0001; /* re-enable DMA */
232 else 237 return 0;
233 { /* by chance we have played to a frame boundary */ 238 }
234 queue_write = queue_read; /* reset the queue */
235 is_playing = false;
236 mp3_play_stop();
237 } 239 }
238 240
241 /* nothing to do, was frame boundary or not our clip */
242 mp3_play_stop();
243 queue_write = queue_read = 0; /* reset the queue */
244
239 return 0; 245 return 0;
240} 246}
241 247
@@ -243,30 +249,42 @@ static int shutup(void)
243/* schedule a clip, at the end or discard the existing queue */ 249/* schedule a clip, at the end or discard the existing queue */
244static int queue_clip(unsigned char* buf, int size, bool enqueue) 250static int queue_clip(unsigned char* buf, int size, bool enqueue)
245{ 251{
252 int queue_level;
253
246 if (!enqueue) 254 if (!enqueue)
247 shutup(); /* cut off all the pending stuff */ 255 shutup(); /* cut off all the pending stuff */
248 256
249 queue[queue_write].buf = buf; 257 if (!size)
250 queue[queue_write].len = size; 258 return 0; /* safety check */
259
260 /* disable the DMA temporarily, to be safe of race condition */
261 CHCR3 &= ~0x0001;
262
263 queue_level = QUEUE_LEVEL; /* check old level */
264
265 if (queue_level < QUEUE_SIZE - 1) /* space left? */
266 {
267 queue[queue_write].buf = buf; /* populate an entry */
268 queue[queue_write].len = size;
251 269
252 /* FixMe: make this IRQ-safe */ 270 queue_write++; /* increase queue */
271 if (queue_write >= QUEUE_SIZE)
272 queue_write = 0;
273 }
253 274
254 if (!is_playing) 275 if (queue_level == 0)
255 { /* queue empty, we have to do the initial start */ 276 { /* queue was empty, we have to do the initial start */
256 int size_now = MIN(size, 0xFFFF); /* DMA can do no more */ 277 sent = MIN(size, 0xFFFF); /* DMA can do no more */
257 is_playing = true; 278 mp3_play_data(buf, sent, mp3_callback);
258 mp3_play_data(buf, size_now, mp3_callback);
259 curr_hd[0] = buf[1]; 279 curr_hd[0] = buf[1];
260 curr_hd[1] = buf[2]; 280 curr_hd[1] = buf[2];
261 curr_hd[2] = buf[3]; 281 curr_hd[2] = buf[3];
262 mp3_play_pause(true); /* kickoff audio */ 282 mp3_play_pause(true); /* kickoff audio */
263 queue[queue_write].buf += size_now;
264 queue[queue_write].len -= size_now;
265 } 283 }
266 284 else
267 queue_write++; 285 {
268 if (queue_write >= QUEUE_SIZE) 286 CHCR3 |= 0x0001; /* re-enable DMA */
269 queue_write = 0; 287 }
270 288
271 return 0; 289 return 0;
272} 290}
@@ -282,29 +300,30 @@ void talk_init(void)
282 if (fd >= 0) /* success */ 300 if (fd >= 0) /* success */
283 { 301 {
284 close(fd); 302 close(fd);
285 has_voicefont = true; 303 has_voicefile = true;
286 } 304 }
287 else 305 else
288 { 306 {
289 has_voicefont = false; /* no voice file available */ 307 has_voicefile = false; /* no voice file available */
290 } 308 }
291 309
292 talk_buffer_steal(); /* abuse this for most of our inits */ 310 talk_buffer_steal(); /* abuse this for most of our inits */
293 queue_write = queue_read = 0;
294} 311}
295 312
296 313
297/* somebody else claims the mp3 buffer, e.g. for regular play/record */ 314/* somebody else claims the mp3 buffer, e.g. for regular play/record */
298int talk_buffer_steal(void) 315int talk_buffer_steal(void)
299{ 316{
300 p_voicefont = NULL; /* indicate no voicefont (trashed) */ 317 mp3_play_stop();
318 queue_write = queue_read = 0; /* reset the queue */
319 p_voicefile = NULL; /* indicate no voicefile (trashed) */
301 p_thumbnail = mp3buf; /* whole space for thumbnail */ 320 p_thumbnail = mp3buf; /* whole space for thumbnail */
302 size_for_thumbnail = mp3end - mp3buf; 321 size_for_thumbnail = mp3end - mp3buf;
303 return 0; 322 return 0;
304} 323}
305 324
306 325
307/* play a voice ID from voicefont */ 326/* play a voice ID from voicefile */
308int talk_id(int id, bool enqueue) 327int talk_id(int id, bool enqueue)
309{ 328{
310 int clipsize; 329 int clipsize;
@@ -314,10 +333,10 @@ int talk_id(int id, bool enqueue)
314 if (mpeg_status()) /* busy, buffer in use */ 333 if (mpeg_status()) /* busy, buffer in use */
315 return -1; 334 return -1;
316 335
317 if (p_voicefont == NULL && has_voicefont) 336 if (p_voicefile == NULL && has_voicefile)
318 load_voicefont(); /* reload needed */ 337 load_voicefile(); /* reload needed */
319 338
320 if (p_voicefont == NULL) /* still no voices? */ 339 if (p_voicefile == NULL) /* still no voices? */
321 return -1; 340 return -1;
322 341
323 if (id == -1) /* -1 is an indication for silence */ 342 if (id == -1) /* -1 is an indication for silence */
@@ -327,7 +346,6 @@ int talk_id(int id, bool enqueue)
327 unit = ((unsigned)id) >> UNIT_SHIFT; 346 unit = ((unsigned)id) >> UNIT_SHIFT;
328 if (unit) 347 if (unit)
329 { /* sign-extend the value */ 348 { /* sign-extend the value */
330 //splash(200, true,"unit=%d", unit);
331 id = (unsigned)id << (32-UNIT_SHIFT); 349 id = (unsigned)id << (32-UNIT_SHIFT);
332 id >>= (32-UNIT_SHIFT); 350 id >>= (32-UNIT_SHIFT);
333 talk_value(id, unit, enqueue); /* speak it */ 351 talk_value(id, unit, enqueue); /* speak it */
@@ -337,21 +355,21 @@ int talk_id(int id, bool enqueue)
337 if (id > VOICEONLY_DELIMITER) 355 if (id > VOICEONLY_DELIMITER)
338 { /* voice-only entries use the second part of the table */ 356 { /* voice-only entries use the second part of the table */
339 id -= VOICEONLY_DELIMITER + 1; 357 id -= VOICEONLY_DELIMITER + 1;
340 if (id >= p_voicefont->id2_max) 358 if (id >= p_voicefile->id2_max)
341 return -1; /* must be newer than we have */ 359 return -1; /* must be newer than we have */
342 id += p_voicefont->id1_max; /* table 2 is behind table 1 */ 360 id += p_voicefile->id1_max; /* table 2 is behind table 1 */
343 } 361 }
344 else 362 else
345 { /* normal use of the first table */ 363 { /* normal use of the first table */
346 if (id >= p_voicefont->id1_max) 364 if (id >= p_voicefile->id1_max)
347 return -1; /* must be newer than we have */ 365 return -1; /* must be newer than we have */
348 } 366 }
349 367
350 clipsize = p_voicefont->index[id].size; 368 clipsize = p_voicefile->index[id].size;
351 if (clipsize == 0) /* clip not included in voicefont */ 369 if (clipsize == 0) /* clip not included in voicefile */
352 return -1; 370 return -1;
353 371
354 clipbuf = mp3buf + p_voicefont->index[id].offset; 372 clipbuf = mp3buf + p_voicefile->index[id].offset;
355 373
356 queue_clip(clipbuf, clipsize, enqueue); 374 queue_clip(clipbuf, clipsize, enqueue);
357 375