diff options
-rw-r--r-- | apps/talk.c | 198 |
1 files changed, 168 insertions, 30 deletions
diff --git a/apps/talk.c b/apps/talk.c index e07dcb8262..7dbfb2ef77 100644 --- a/apps/talk.c +++ b/apps/talk.c | |||
@@ -108,6 +108,26 @@ struct queue_entry /* one entry of the internal queue */ | |||
108 | 108 | ||
109 | /***************** Globals *****************/ | 109 | /***************** Globals *****************/ |
110 | 110 | ||
111 | #if CONFIG_STORAGE & STORAGE_MMC | ||
112 | /* The MMC storage on the Ondios is slow enough that we want to buffer the | ||
113 | * talk clips only when they are needed */ | ||
114 | # define TALK_PROGRESSIVE_LOAD | ||
115 | #elif CONFIG_CODEC == SWCODEC && MEM <= 2 | ||
116 | /* The entire voice file wouldn't fit in memory together with codecs, so we | ||
117 | * load clips each time they are accessed */ | ||
118 | # define TALK_PARTIAL_LOAD | ||
119 | #endif | ||
120 | |||
121 | #ifdef TALK_PARTIAL_LOAD | ||
122 | static unsigned char *clip_buffer; | ||
123 | static long max_clipsize; /* size of the biggest clip */ | ||
124 | static long buffered_id[QUEUE_SIZE]; /* IDs of the talk clips */ | ||
125 | static uint8_t clip_age[QUEUE_SIZE]; | ||
126 | #if QUEUE_SIZE > 255 | ||
127 | # error clip_age[] type too small | ||
128 | #endif | ||
129 | #endif | ||
130 | |||
111 | static unsigned char* p_thumbnail = NULL; /* buffer for thumbnails */ | 131 | static unsigned char* p_thumbnail = NULL; /* buffer for thumbnails */ |
112 | /* Multiple thumbnails can be loaded back-to-back in this buffer. */ | 132 | /* Multiple thumbnails can be loaded back-to-back in this buffer. */ |
113 | static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in | 133 | static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in |
@@ -131,7 +151,7 @@ static struct mutex queue_mutex SHAREDBSS_ATTR; | |||
131 | #endif /* CONFIG_CODEC */ | 151 | #endif /* CONFIG_CODEC */ |
132 | static int sent; /* how many bytes handed over to playback, owned by ISR */ | 152 | static int sent; /* how many bytes handed over to playback, owned by ISR */ |
133 | static unsigned char curr_hd[3]; /* current frame header, for re-sync */ | 153 | static unsigned char curr_hd[3]; /* current frame header, for re-sync */ |
134 | static int filehandle = -1; /* global, so the MMC variant can keep the file open */ | 154 | static int filehandle = -1; /* global, so we can keep the file open if needed */ |
135 | static unsigned char* p_silence; /* VOICE_PAUSE clip, used for termination */ | 155 | static unsigned char* p_silence; /* VOICE_PAUSE clip, used for termination */ |
136 | static long silence_len; /* length of the VOICE_PAUSE clip */ | 156 | static long silence_len; /* length of the VOICE_PAUSE clip */ |
137 | static unsigned char* p_lastclip; /* address of latest clip, for silence add */ | 157 | static unsigned char* p_lastclip; /* address of latest clip, for silence add */ |
@@ -182,22 +202,78 @@ static unsigned char* get_clip(long id, long* p_size) | |||
182 | clipsize = p_voicefile->index[id].size; | 202 | clipsize = p_voicefile->index[id].size; |
183 | if (clipsize == 0) /* clip not included in voicefile */ | 203 | if (clipsize == 0) /* clip not included in voicefile */ |
184 | return NULL; | 204 | return NULL; |
205 | |||
206 | #ifndef TALK_PARTIAL_LOAD | ||
185 | clipbuf = (unsigned char *) p_voicefile + p_voicefile->index[id].offset; | 207 | clipbuf = (unsigned char *) p_voicefile + p_voicefile->index[id].offset; |
208 | #endif | ||
186 | 209 | ||
187 | #if (CONFIG_STORAGE & STORAGE_MMC) /* dynamic loading, on demand */ | 210 | #if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) |
188 | if (!(clipsize & LOADED_MASK)) | 211 | if (!(clipsize & LOADED_MASK)) |
189 | { /* clip used for the first time, needs loading */ | 212 | { /* clip needs loading */ |
213 | #ifdef TALK_PARTIAL_LOAD | ||
214 | int idx = 0; | ||
215 | if (id == VOICE_PAUSE) { | ||
216 | idx = QUEUE_SIZE; /* we keep VOICE_PAUSE loaded */ | ||
217 | } else { | ||
218 | int oldest = 0, i; | ||
219 | for(i=0; i<QUEUE_SIZE; i++) { | ||
220 | if (buffered_id[i] < 0) { | ||
221 | /* found a free entry, that means the buffer isn't | ||
222 | * full yet. */ | ||
223 | idx = i; | ||
224 | break; | ||
225 | } | ||
226 | |||
227 | /* find the oldest clip */ | ||
228 | if(clip_age[i] > oldest) { | ||
229 | idx = i; | ||
230 | oldest = clip_age[i]; | ||
231 | } | ||
232 | |||
233 | /* increment age of each loaded clip */ | ||
234 | clip_age[i]++; | ||
235 | } | ||
236 | clip_age[idx] = 0; /* reset clip's age */ | ||
237 | } | ||
238 | clipbuf = clip_buffer + idx * max_clipsize; | ||
239 | #endif | ||
240 | |||
190 | lseek(filehandle, p_voicefile->index[id].offset, SEEK_SET); | 241 | lseek(filehandle, p_voicefile->index[id].offset, SEEK_SET); |
191 | if (read(filehandle, clipbuf, clipsize) != clipsize) | 242 | if (read(filehandle, clipbuf, clipsize) != clipsize) |
192 | return NULL; /* read error */ | 243 | return NULL; /* read error */ |
193 | 244 | ||
194 | p_voicefile->index[id].size |= LOADED_MASK; /* mark as loaded */ | 245 | p_voicefile->index[id].size |= LOADED_MASK; /* mark as loaded */ |
246 | |||
247 | #ifdef TALK_PARTIAL_LOAD | ||
248 | if (id != VOICE_PAUSE) { | ||
249 | if (buffered_id[idx] >= 0) { | ||
250 | /* mark previously loaded clip as unloaded */ | ||
251 | p_voicefile->index[buffered_id[idx]].size &= ~LOADED_MASK; | ||
252 | } | ||
253 | buffered_id[idx] = id; | ||
254 | } | ||
255 | #endif | ||
195 | } | 256 | } |
196 | else | 257 | else |
197 | { /* clip is in memory already */ | 258 | { /* clip is in memory already */ |
259 | #ifdef TALK_PARTIAL_LOAD | ||
260 | /* Find where it was loaded */ | ||
261 | clipbuf = clip_buffer; | ||
262 | if (id == VOICE_PAUSE) { | ||
263 | clipbuf += QUEUE_SIZE * max_clipsize; | ||
264 | } else { | ||
265 | int idx; | ||
266 | for (idx=0; idx<QUEUE_SIZE; idx++) | ||
267 | if (buffered_id[idx] == id) { | ||
268 | clipbuf += idx * max_clipsize; | ||
269 | clip_age[idx] = 0; /* reset clip's age */ | ||
270 | break; | ||
271 | } | ||
272 | } | ||
273 | #endif | ||
198 | clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */ | 274 | clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */ |
199 | } | 275 | } |
200 | #endif | 276 | #endif /* defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) */ |
201 | 277 | ||
202 | *p_size = clipsize; | 278 | *p_size = clipsize; |
203 | return clipbuf; | 279 | return clipbuf; |
@@ -205,29 +281,40 @@ static unsigned char* get_clip(long id, long* p_size) | |||
205 | 281 | ||
206 | 282 | ||
207 | /* load the voice file into the mp3 buffer */ | 283 | /* load the voice file into the mp3 buffer */ |
208 | static void load_voicefile(void) | 284 | static void load_voicefile(bool probe) |
209 | { | 285 | { |
210 | int load_size; | 286 | int load_size; |
211 | int got_size; | 287 | int got_size; |
288 | #ifndef TALK_PARTIAL_LOAD | ||
212 | int file_size; | 289 | int file_size; |
290 | #endif | ||
213 | #ifdef ROCKBOX_LITTLE_ENDIAN | 291 | #ifdef ROCKBOX_LITTLE_ENDIAN |
214 | int i; | 292 | int i; |
215 | #endif | 293 | #endif |
216 | 294 | ||
217 | filehandle = open_voicefile(); | 295 | if (!probe) |
296 | filehandle = open_voicefile(); | ||
218 | if (filehandle < 0) /* failed to open */ | 297 | if (filehandle < 0) /* failed to open */ |
219 | goto load_err; | 298 | goto load_err; |
220 | 299 | ||
300 | #ifndef TALK_PARTIAL_LOAD | ||
221 | file_size = filesize(filehandle); | 301 | file_size = filesize(filehandle); |
222 | if (file_size > audiobufend - audiobuf) /* won't fit? */ | 302 | if (file_size > audiobufend - audiobuf) /* won't fit? */ |
223 | goto load_err; | 303 | goto load_err; |
304 | #endif | ||
224 | 305 | ||
225 | #if (CONFIG_STORAGE & STORAGE_MMC) /* load only the header for now */ | 306 | #if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) |
307 | /* load only the header for now */ | ||
226 | load_size = offsetof(struct voicefile, index); | 308 | load_size = offsetof(struct voicefile, index); |
227 | #else /* load the full file */ | 309 | #else /* load the full file */ |
228 | load_size = file_size; | 310 | load_size = file_size; |
229 | #endif | 311 | #endif |
230 | 312 | ||
313 | #ifdef TALK_PARTIAL_LOAD | ||
314 | if (load_size > audiobufend - audiobuf) /* won't fit? */ | ||
315 | goto load_err; | ||
316 | #endif | ||
317 | |||
231 | got_size = read(filehandle, audiobuf, load_size); | 318 | got_size = read(filehandle, audiobuf, load_size); |
232 | if (got_size != load_size /* failure */) | 319 | if (got_size != load_size /* failure */) |
233 | goto load_err; | 320 | goto load_err; |
@@ -258,26 +345,39 @@ static void load_voicefile(void) | |||
258 | else | 345 | else |
259 | goto load_err; | 346 | goto load_err; |
260 | 347 | ||
261 | #ifdef ROCKBOX_LITTLE_ENDIAN | 348 | #if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) |
262 | for (i = 0; i < p_voicefile->id1_max + p_voicefile->id2_max; i++) | ||
263 | structec_convert(&p_voicefile->index[i], "ll", 1, true); | ||
264 | #endif | ||
265 | |||
266 | #if (CONFIG_STORAGE & STORAGE_MMC) | ||
267 | /* load the index table, now that we know its size from the header */ | 349 | /* load the index table, now that we know its size from the header */ |
268 | load_size = (p_voicefile->id1_max + p_voicefile->id2_max) | 350 | load_size = (p_voicefile->id1_max + p_voicefile->id2_max) |
269 | * sizeof(struct clip_entry); | 351 | * sizeof(struct clip_entry); |
352 | |||
353 | #ifdef TALK_PARTIAL_LOAD | ||
354 | if (load_size > audiobufend - audiobuf) /* won't fit? */ | ||
355 | goto load_err; | ||
356 | #endif | ||
357 | |||
270 | got_size = read(filehandle, | 358 | got_size = read(filehandle, |
271 | (unsigned char *) p_voicefile + offsetof(struct voicefile, index), load_size); | 359 | (unsigned char *) p_voicefile + offsetof(struct voicefile, index), load_size); |
272 | if (got_size != load_size) /* read error */ | 360 | if (got_size != load_size) /* read error */ |
273 | goto load_err; | 361 | goto load_err; |
274 | #else | 362 | #else |
275 | close(filehandle); /* only the MMC variant leaves it open */ | 363 | close(filehandle); |
276 | filehandle = -1; | 364 | filehandle = -1; |
365 | #endif /* defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) */ | ||
366 | |||
367 | #ifdef ROCKBOX_LITTLE_ENDIAN | ||
368 | for (i = 0; i < p_voicefile->id1_max + p_voicefile->id2_max; i++) | ||
369 | structec_convert(&p_voicefile->index[i], "ll", 1, true); | ||
277 | #endif | 370 | #endif |
278 | 371 | ||
279 | /* make sure to have the silence clip, if available */ | 372 | #ifdef TALK_PARTIAL_LOAD |
280 | p_silence = get_clip(VOICE_PAUSE, &silence_len); | 373 | clip_buffer = (unsigned char *) p_voicefile + p_voicefile->table; |
374 | unsigned clips = p_voicefile->id1_max + p_voicefile->id2_max; | ||
375 | clip_buffer += clips * sizeof(struct clip_entry); /* skip index */ | ||
376 | #endif | ||
377 | if (!probe) { | ||
378 | /* make sure to have the silence clip, if available */ | ||
379 | p_silence = get_clip(VOICE_PAUSE, &silence_len); | ||
380 | } | ||
281 | 381 | ||
282 | return; | 382 | return; |
283 | 383 | ||
@@ -501,6 +601,13 @@ static void reset_state(void) | |||
501 | p_thumbnail = audiobuf; | 601 | p_thumbnail = audiobuf; |
502 | size_for_thumbnail = audiobufend - audiobuf; | 602 | size_for_thumbnail = audiobufend - audiobuf; |
503 | #endif | 603 | #endif |
604 | |||
605 | #ifdef TALK_PARTIAL_LOAD | ||
606 | int i; | ||
607 | for(i=0; i<QUEUE_SIZE; i++) | ||
608 | buffered_id[i] = -1; | ||
609 | #endif | ||
610 | |||
504 | thumbnail_buf_used = 0; | 611 | thumbnail_buf_used = 0; |
505 | p_silence = NULL; /* pause clip not accessible */ | 612 | p_silence = NULL; /* pause clip not accessible */ |
506 | } | 613 | } |
@@ -517,8 +624,8 @@ void talk_init(void) | |||
517 | return; | 624 | return; |
518 | } | 625 | } |
519 | 626 | ||
520 | #if (CONFIG_STORAGE & STORAGE_MMC) | 627 | #if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) |
521 | if (filehandle >= 0) /* MMC: An old voice file might still be open */ | 628 | if (filehandle >= 0) |
522 | { | 629 | { |
523 | close(filehandle); | 630 | close(filehandle); |
524 | filehandle = -1; | 631 | filehandle = -1; |
@@ -540,18 +647,49 @@ void talk_init(void) | |||
540 | reset_state(); /* use this for most of our inits */ | 647 | reset_state(); /* use this for most of our inits */ |
541 | 648 | ||
542 | filehandle = open_voicefile(); | 649 | filehandle = open_voicefile(); |
543 | size_t audiobufsz = audiobufend - audiobuf; | 650 | if (filehandle < 0) { |
651 | has_voicefile = false; | ||
652 | voicefile_size = 0; | ||
653 | return; | ||
654 | } | ||
655 | |||
656 | voicefile_size = filesize(filehandle); | ||
657 | |||
544 | /* test if we can open and if it fits in the audiobuffer */ | 658 | /* test if we can open and if it fits in the audiobuffer */ |
545 | has_voicefile = filehandle >= 0 && filesize(filehandle) <= (off_t)audiobufsz; | 659 | size_t audiobufsz = audiobufend - audiobuf; |
546 | voicefile_size = 0; | ||
547 | 660 | ||
548 | if (has_voicefile) | 661 | #ifdef TALK_PARTIAL_LOAD |
549 | { | 662 | /* we won't load the full file, we only need the index */ |
550 | voicefile_size = filesize(filehandle); | 663 | load_voicefile(true); |
551 | close(filehandle); /* close again, this was just to detect presence */ | 664 | if (!p_voicefile) |
552 | filehandle = -1; | 665 | return; |
666 | |||
667 | unsigned clips = p_voicefile->id1_max + p_voicefile->id2_max; | ||
668 | unsigned i; | ||
669 | int silence_size = 0; | ||
670 | |||
671 | for(i=0; i<clips; i++) { | ||
672 | int size = p_voicefile->index[i].size; | ||
673 | if (size > max_clipsize) | ||
674 | max_clipsize = size; | ||
675 | if (i == VOICE_PAUSE) | ||
676 | silence_size = size; | ||
553 | } | 677 | } |
554 | 678 | ||
679 | voicefile_size = p_voicefile->table + clips * sizeof(struct clip_entry); | ||
680 | voicefile_size += max_clipsize * QUEUE_SIZE + silence_size; | ||
681 | p_voicefile = NULL; /* Don't pretend we can load talk clips just yet */ | ||
682 | #endif | ||
683 | |||
684 | if (voicefile_size <= audiobufsz) { | ||
685 | has_voicefile = true; | ||
686 | } else { | ||
687 | has_voicefile = false; | ||
688 | voicefile_size = 0; | ||
689 | } | ||
690 | |||
691 | close(filehandle); /* close again, this was just to detect presence */ | ||
692 | filehandle = -1; | ||
555 | } | 693 | } |
556 | 694 | ||
557 | #if CONFIG_CODEC == SWCODEC | 695 | #if CONFIG_CODEC == SWCODEC |
@@ -576,8 +714,8 @@ void talk_buffer_steal(void) | |||
576 | #if CONFIG_CODEC != SWCODEC | 714 | #if CONFIG_CODEC != SWCODEC |
577 | mp3_play_stop(); | 715 | mp3_play_stop(); |
578 | #endif | 716 | #endif |
579 | #if (CONFIG_STORAGE & STORAGE_MMC) | 717 | #if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) |
580 | if (filehandle >= 0) /* only relevant for MMC */ | 718 | if (filehandle >= 0) |
581 | { | 719 | { |
582 | close(filehandle); | 720 | close(filehandle); |
583 | filehandle = -1; | 721 | filehandle = -1; |
@@ -603,7 +741,7 @@ int talk_id(int32_t id, bool enqueue) | |||
603 | #endif | 741 | #endif |
604 | 742 | ||
605 | if (p_voicefile == NULL && has_voicefile) | 743 | if (p_voicefile == NULL && has_voicefile) |
606 | load_voicefile(); /* reload needed */ | 744 | load_voicefile(false); /* reload needed */ |
607 | 745 | ||
608 | if (p_voicefile == NULL) /* still no voices? */ | 746 | if (p_voicefile == NULL) /* still no voices? */ |
609 | return -1; | 747 | return -1; |
@@ -1134,4 +1272,4 @@ void talk_time(const struct tm *tm, bool enqueue) | |||
1134 | } | 1272 | } |
1135 | } | 1273 | } |
1136 | 1274 | ||
1137 | #endif | 1275 | #endif /* CONFIG_RTC */ |