diff options
author | Thomas Martitz <kugel@rockbox.org> | 2013-11-21 11:44:04 +0100 |
---|---|---|
committer | Thomas Martitz <kugel@rockbox.org> | 2014-02-02 19:40:39 +0100 |
commit | c46f9be10a9b3f34f804583d8cdc980ab62c58bd (patch) | |
tree | 73f425a50ece157d888e288881d45fe402f1faca /apps | |
parent | dac40fdd60872da55b60ae9912b31e6549f1cf4a (diff) | |
download | rockbox-c46f9be10a9b3f34f804583d8cdc980ab62c58bd.tar.gz rockbox-c46f9be10a9b3f34f804583d8cdc980ab62c58bd.zip |
talk: Smarter cache management for TALK_PARTIAL_LOAD.
Previously the clip cache of TALK_PARTIAL_LOAD reserved space N clips, each slot
was as big as the maximum sized clip which was necessary to replace clips
in-memory in MRU-style.
The cache management now uses buflib to allocate and free each clip, using the
clip's real size. This allows the clip cache to be much more compact, because
no space is wasted for the max. sized clip. This makes use of buflib's ability
to easily manage differently-sized memory chunks by moving them to make free
space.
As an example: for english.voice TALK_PARTIAL_LOAD allocated 288k in advance.
for just 64 clips. With this patch ~70 clips can be stored in a 100k buffer.
This, the memory usage is cut by 2/3 and almost optimal (there's still the
buflib per-alloc cookie overhead).
As a result the TALK_PARTIAL_LOAD buffer is restricted to 100k which still
allows for more clips than previously, on average.
Change-Id: I257654071e9a95770cd6db2c2765f020befce412
Diffstat (limited to 'apps')
-rw-r--r-- | apps/talk.c | 655 |
1 files changed, 373 insertions, 282 deletions
diff --git a/apps/talk.c b/apps/talk.c index 0904e41c3b..4c4e6184b8 100644 --- a/apps/talk.c +++ b/apps/talk.c | |||
@@ -87,6 +87,8 @@ const char* const file_thumbnail_ext = ".talk"; | |||
87 | #define MAX_THUMBNAIL_BUFSIZE 0x10000 | 87 | #define MAX_THUMBNAIL_BUFSIZE 0x10000 |
88 | #endif | 88 | #endif |
89 | 89 | ||
90 | #define DEFAULT_VOICE_LANG "english" | ||
91 | |||
90 | /***************** Data types *****************/ | 92 | /***************** Data types *****************/ |
91 | 93 | ||
92 | struct clip_entry /* one entry of the index table */ | 94 | struct clip_entry /* one entry of the index table */ |
@@ -114,16 +116,7 @@ struct voicefile_header /* file format of our voice file */ | |||
114 | * The Ondios have slow storage access and loading the entire voice file would | 116 | * The Ondios have slow storage access and loading the entire voice file would |
115 | * take several seconds, so we use the same mechanism. */ | 117 | * take several seconds, so we use the same mechanism. */ |
116 | #define TALK_PARTIAL_LOAD | 118 | #define TALK_PARTIAL_LOAD |
117 | #endif | 119 | #define MAX_CLIP_BUFFER_SIZE 100000 /* 70+ clips should fit into 100k */ |
118 | |||
119 | #ifdef TALK_PARTIAL_LOAD | ||
120 | static long max_clipsize; /* size of the biggest clip */ | ||
121 | static long buffered_id[QUEUE_SIZE]; /* IDs of the talk clips */ | ||
122 | static uint8_t clip_age[QUEUE_SIZE]; | ||
123 | #if QUEUE_SIZE > 255 | ||
124 | # error clip_age[] type too small | ||
125 | #endif | ||
126 | static int cache_hits, cache_misses; | ||
127 | #endif | 120 | #endif |
128 | 121 | ||
129 | /* Multiple thumbnails can be loaded back-to-back in this buffer. */ | 122 | /* Multiple thumbnails can be loaded back-to-back in this buffer. */ |
@@ -147,9 +140,6 @@ static struct mutex queue_mutex SHAREDBSS_ATTR; | |||
147 | #endif /* CONFIG_CODEC */ | 140 | #endif /* CONFIG_CODEC */ |
148 | static int sent; /* how many bytes handed over to playback, owned by ISR */ | 141 | static int sent; /* how many bytes handed over to playback, owned by ISR */ |
149 | static unsigned char curr_hd[3]; /* current frame header, for re-sync */ | 142 | static unsigned char curr_hd[3]; /* current frame header, for re-sync */ |
150 | static int silence_offset; /* VOICE_PAUSE clip, used for termination */ | ||
151 | static long silence_length; /* length of the VOICE_PAUSE clip */ | ||
152 | static unsigned long lastclip_offset; /* address of latest clip, for silence add */ | ||
153 | static unsigned char last_lang[MAX_FILENAME+1]; /* name of last used lang file (in talk_init) */ | 143 | static unsigned char last_lang[MAX_FILENAME+1]; /* name of last used lang file (in talk_init) */ |
154 | static bool talk_initialized; /* true if talk_init has been called */ | 144 | static bool talk_initialized; /* true if talk_init has been called */ |
155 | static bool give_buffer_away; /* true if we should give the buffers away in shrink_callback if requested */ | 145 | static bool give_buffer_away; /* true if we should give the buffers away in shrink_callback if requested */ |
@@ -161,27 +151,65 @@ static unsigned long voicefile_size; | |||
161 | 151 | ||
162 | struct queue_entry /* one entry of the internal queue */ | 152 | struct queue_entry /* one entry of the internal queue */ |
163 | { | 153 | { |
164 | int offset, length; | 154 | int offset; /* actually a buflib handle if type == HANDLE */ |
155 | int length; /* total length of the clip */ | ||
156 | int remaining; /* bytes that still need to be deoded */ | ||
157 | /* for large QUEUE_SIZE values it might be worthwhile to merge the type | ||
158 | * into the bits of the above members (as a space saver). For small values | ||
159 | * the required extra code outweights this so it's not done here */ | ||
160 | enum offset_type { | ||
161 | TALK_OFFSET, | ||
162 | THUMB_OFFSET, | ||
163 | #ifdef TALK_PARTIAL_LOAD | ||
164 | TALK_HANDLE, | ||
165 | #endif | ||
166 | } type; | ||
165 | }; | 167 | }; |
166 | 168 | ||
167 | static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ | 169 | #ifdef TALK_PARTIAL_LOAD |
170 | static struct buflib_context clip_ctx; | ||
168 | 171 | ||
169 | #define DEFAULT_VOICE_LANG "english" | 172 | struct clip_cache_metadata { |
173 | long tick; | ||
174 | int handle, voice_id; | ||
175 | }; | ||
176 | |||
177 | static int metadata_table_handle; | ||
178 | static unsigned max_clips; | ||
179 | static int cache_hits, cache_misses; | ||
180 | #endif | ||
181 | |||
182 | static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ | ||
183 | static struct queue_entry silence, *last_clip; | ||
170 | 184 | ||
171 | /***************** Private implementation *****************/ | 185 | /***************** Private implementation *****************/ |
172 | 186 | ||
173 | static int thumb_handle; | 187 | static int index_handle, talk_handle, thumb_handle; |
174 | static int talk_handle, talk_handle_locked; | 188 | |
189 | static int move_callback(int handle, void *current, void *new) | ||
190 | { | ||
191 | (void)handle; (void)current; (void)new; | ||
192 | #ifdef TALK_PARTIAL_LOAD | ||
193 | if (handle == talk_handle) | ||
194 | if (!buflib_context_relocate(&clip_ctx, new)) | ||
195 | return BUFLIB_CB_CANNOT_MOVE; | ||
196 | #endif | ||
197 | return BUFLIB_CB_OK; | ||
198 | } | ||
199 | |||
200 | |||
201 | static struct mutex read_buffer_mutex; | ||
175 | 202 | ||
176 | #if CONFIG_CODEC != SWCODEC | ||
177 | 203 | ||
178 | /* on HWCODEC only voice xor audio can be active at a time */ | 204 | /* on HWCODEC only voice xor audio can be active at a time */ |
179 | static bool check_audio_status(void) | 205 | static bool check_audio_status(void) |
180 | { | 206 | { |
207 | #if CONFIG_CODEC != SWCODEC | ||
181 | if (audio_status()) /* busy, buffer in use */ | 208 | if (audio_status()) /* busy, buffer in use */ |
182 | return false; | 209 | return false; |
183 | /* ensure playback is given up on the buffer */ | 210 | /* ensure playback is given up on the buffer */ |
184 | audio_hard_stop(); | 211 | audio_hard_stop(); |
212 | #endif | ||
185 | return true; | 213 | return true; |
186 | } | 214 | } |
187 | 215 | ||
@@ -190,31 +218,43 @@ static bool check_audio_status(void) | |||
190 | static void sync_callback(int handle, bool sync_on) | 218 | static void sync_callback(int handle, bool sync_on) |
191 | { | 219 | { |
192 | (void) handle; | 220 | (void) handle; |
193 | (void) sync_on; | ||
194 | #if CONFIG_CPU == SH7034 | ||
195 | if (sync_on) | 221 | if (sync_on) |
196 | CHCR3 &= ~0x0001; /* disable the DMA (and therefore the interrupt also) */ | 222 | mutex_lock(&read_buffer_mutex); |
197 | else | 223 | else |
198 | CHCR3 |= 0x0001; /* re-enable the DMA */ | 224 | mutex_unlock(&read_buffer_mutex); |
199 | #endif | ||
200 | } | 225 | } |
201 | #else | ||
202 | #define check_audio_status() (true) | ||
203 | #endif | ||
204 | 226 | ||
205 | static int move_callback(int handle, void *current, void *new) | 227 | static ssize_t read_to_handle_ex(int fd, struct buflib_context *ctx, int handle, |
228 | int handle_offset, size_t count) | ||
206 | { | 229 | { |
207 | (void)handle;(void)current;(void)new; | 230 | unsigned char *buf; |
208 | if (UNLIKELY(talk_handle_locked)) | 231 | ssize_t ret; |
209 | return BUFLIB_CB_CANNOT_MOVE; | 232 | mutex_lock(&read_buffer_mutex); |
210 | return BUFLIB_CB_OK; | 233 | |
234 | if (!ctx) | ||
235 | buf = core_get_data(handle); | ||
236 | else | ||
237 | buf = buflib_get_data(ctx, handle); | ||
238 | |||
239 | buf += handle_offset; | ||
240 | ret = read(fd, buf, count); | ||
241 | |||
242 | mutex_unlock(&read_buffer_mutex); | ||
243 | |||
244 | return ret; | ||
211 | } | 245 | } |
212 | 246 | ||
213 | static int clip_shrink_callback(int handle, unsigned hints, void *start, size_t old_size) | 247 | static ssize_t read_to_handle(int fd, int handle, int handle_offset, size_t count) |
214 | { | 248 | { |
215 | (void)start;(void)old_size;(void)hints; | 249 | return read_to_handle_ex(fd, NULL, handle, handle_offset, count); |
250 | } | ||
251 | |||
216 | 252 | ||
217 | #if (!defined(TALK_PARTIAL_LOAD) || (MEMORYSIZE > 2)) | 253 | static int shrink_callback(int handle, unsigned hints, void *start, size_t old_size) |
254 | { | ||
255 | (void)start;(void)old_size;(void)hints; | ||
256 | int *h; | ||
257 | #if (MAX_CLIP_BUFFER_SIZE < (MEMORYSIZE<<20) || (MEMORYSIZE > 2)) | ||
218 | /* on low-mem and when the voice buffer size is not limited (i.e. | 258 | /* on low-mem and when the voice buffer size is not limited (i.e. |
219 | * on 2MB HWCODEC) we effectively own the entire buffer because | 259 | * on 2MB HWCODEC) we effectively own the entire buffer because |
220 | * the voicefile takes up all RAM. This blocks other Rockbox parts | 260 | * the voicefile takes up all RAM. This blocks other Rockbox parts |
@@ -222,12 +262,22 @@ static int clip_shrink_callback(int handle, unsigned hints, void *start, size_t | |||
222 | * up the buffer and reload when clips are played back. On high-mem | 262 | * up the buffer and reload when clips are played back. On high-mem |
223 | * or when the clip buffer is limited to a few 100K this provision is | 263 | * or when the clip buffer is limited to a few 100K this provision is |
224 | * not necessary. */ | 264 | * not necessary. */ |
225 | if (LIKELY(!talk_handle_locked) | 265 | if (give_buffer_away |
226 | && give_buffer_away | ||
227 | && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) | 266 | && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) |
228 | #endif | 267 | #endif |
229 | { | 268 | { |
230 | talk_handle = core_free(handle); | 269 | if (handle == talk_handle) |
270 | h = &talk_handle; | ||
271 | else //if (handle == index_handle) | ||
272 | h = &index_handle; | ||
273 | |||
274 | mutex_lock(&read_buffer_mutex); | ||
275 | /* the clip buffer isn't usable without index table */ | ||
276 | if (handle == index_handle && talk_handle > 0) | ||
277 | talk_handle = core_free(talk_handle); | ||
278 | *h = core_free(handle); | ||
279 | mutex_unlock(&read_buffer_mutex); | ||
280 | |||
231 | return BUFLIB_CB_OK; | 281 | return BUFLIB_CB_OK; |
232 | } | 282 | } |
233 | return BUFLIB_CB_CANNOT_SHRINK; | 283 | return BUFLIB_CB_CANNOT_SHRINK; |
@@ -237,62 +287,31 @@ static int thumb_shrink_callback(int handle, unsigned hints, void *start, size_t | |||
237 | { | 287 | { |
238 | (void)start;(void)old_size;(void)hints; | 288 | (void)start;(void)old_size;(void)hints; |
239 | 289 | ||
240 | /* be generous about the thumbnail buffer unless currently used */ | 290 | if (handle == thumb_handle && thumbnail_buf_used == 0) |
241 | if (LIKELY(!talk_handle_locked) && thumbnail_buf_used == 0) | ||
242 | { | 291 | { |
292 | mutex_lock(&read_buffer_mutex); | ||
243 | thumb_handle = core_free(handle); | 293 | thumb_handle = core_free(handle); |
294 | mutex_unlock(&read_buffer_mutex); | ||
244 | return BUFLIB_CB_OK; | 295 | return BUFLIB_CB_OK; |
245 | } | 296 | } |
297 | |||
246 | return BUFLIB_CB_CANNOT_SHRINK; | 298 | return BUFLIB_CB_CANNOT_SHRINK; |
247 | } | 299 | } |
248 | 300 | ||
249 | static struct buflib_callbacks clip_ops = { | 301 | |
302 | |||
303 | static struct buflib_callbacks talk_ops = { | ||
250 | .move_callback = move_callback, | 304 | .move_callback = move_callback, |
251 | #if CONFIG_CODEC != SWCODEC | ||
252 | .sync_callback = sync_callback, | 305 | .sync_callback = sync_callback, |
253 | #endif | 306 | .shrink_callback = shrink_callback, |
254 | .shrink_callback = clip_shrink_callback, | ||
255 | }; | 307 | }; |
256 | 308 | ||
257 | static struct buflib_callbacks thumb_ops = { | 309 | static struct buflib_callbacks thumb_ops = { |
258 | .move_callback = move_callback, | 310 | .move_callback = move_callback, |
259 | #if CONFIG_CODEC != SWCODEC | ||
260 | .sync_callback = sync_callback, | 311 | .sync_callback = sync_callback, |
261 | #endif | ||
262 | .shrink_callback = thumb_shrink_callback, | 312 | .shrink_callback = thumb_shrink_callback, |
263 | }; | 313 | }; |
264 | 314 | ||
265 | |||
266 | static int index_handle, index_handle_locked; | ||
267 | static int index_move_callback(int handle, void *current, void *new) | ||
268 | { | ||
269 | (void)handle;(void)current;(void)new; | ||
270 | if (UNLIKELY(index_handle_locked)) | ||
271 | return BUFLIB_CB_CANNOT_MOVE; | ||
272 | return BUFLIB_CB_OK; | ||
273 | } | ||
274 | |||
275 | static int index_shrink_callback(int handle, unsigned hints, void *start, size_t old_size) | ||
276 | { | ||
277 | (void)start;(void)old_size; | ||
278 | if (LIKELY(!index_handle_locked) | ||
279 | && give_buffer_away | ||
280 | && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) | ||
281 | { | ||
282 | index_handle = core_free(handle); | ||
283 | /* the clip buffer isn't usable without index table */ | ||
284 | if (LIKELY(!talk_handle_locked)) | ||
285 | talk_handle = core_free(talk_handle); | ||
286 | return BUFLIB_CB_OK; | ||
287 | } | ||
288 | return BUFLIB_CB_CANNOT_SHRINK; | ||
289 | } | ||
290 | |||
291 | static struct buflib_callbacks index_ops = { | ||
292 | .move_callback = index_move_callback, | ||
293 | .shrink_callback = index_shrink_callback, | ||
294 | }; | ||
295 | |||
296 | static int open_voicefile(void) | 315 | static int open_voicefile(void) |
297 | { | 316 | { |
298 | char buf[64]; | 317 | char buf[64]; |
@@ -310,114 +329,143 @@ static int open_voicefile(void) | |||
310 | } | 329 | } |
311 | 330 | ||
312 | 331 | ||
313 | /* fetch a clip from the voice file */ | 332 | static int id2index(int id) |
314 | static int get_clip(long id, long* p_size) | ||
315 | { | 333 | { |
316 | int retval = -1; | 334 | int index = id; |
317 | struct clip_entry* clipbuf; | ||
318 | size_t clipsize; | ||
319 | |||
320 | if (id > VOICEONLY_DELIMITER) | 335 | if (id > VOICEONLY_DELIMITER) |
321 | { /* voice-only entries use the second part of the table. | 336 | { /* voice-only entries use the second part of the table. |
322 | The first string comes after VOICEONLY_DELIMITER so we need to | 337 | The first string comes after VOICEONLY_DELIMITER so we need to |
323 | substract VOICEONLY_DELIMITER + 1 */ | 338 | substract VOICEONLY_DELIMITER + 1 */ |
324 | id -= VOICEONLY_DELIMITER + 1; | 339 | index -= VOICEONLY_DELIMITER + 1; |
325 | if (id >= voicefile.id2_max) | 340 | if (index >= voicefile.id2_max) |
326 | return -1; /* must be newer than we have */ | 341 | return -1; /* must be newer than we have */ |
327 | id += voicefile.id1_max; /* table 2 is behind table 1 */ | 342 | index += voicefile.id1_max; /* table 2 is behind table 1 */ |
328 | } | 343 | } |
329 | else | 344 | else |
330 | { /* normal use of the first table */ | 345 | { /* normal use of the first table */ |
331 | if (id >= voicefile.id1_max) | 346 | if (id >= voicefile.id1_max) |
332 | return -1; /* must be newer than we have */ | 347 | return -1; /* must be newer than we have */ |
333 | } | 348 | } |
349 | return index; | ||
350 | } | ||
351 | |||
352 | #ifdef TALK_PARTIAL_LOAD | ||
353 | static int free_oldest_clip(void) | ||
354 | { | ||
355 | unsigned i; | ||
356 | int oldest = 0; | ||
357 | long age, now; | ||
358 | struct clip_entry* clipbuf; | ||
359 | struct clip_cache_metadata *cc = buflib_get_data(&clip_ctx, metadata_table_handle); | ||
360 | for(age = i = 0, now = current_tick; i < max_clips; i++) | ||
361 | { | ||
362 | if (cc[i].handle && (now - cc[i].tick) > age | ||
363 | && cc[i].voice_id != VOICE_PAUSE) /* never consider silence */ | ||
364 | { | ||
365 | age = now - cc[i].tick; | ||
366 | oldest = i; | ||
367 | } | ||
368 | } | ||
369 | cc = &cc[oldest]; | ||
370 | cc->handle = buflib_free(&clip_ctx, cc->handle); | ||
371 | /* need to clear the LOADED bit too */ | ||
372 | clipbuf = core_get_data(index_handle); | ||
373 | clipbuf[id2index(cc->voice_id)].size &= ~LOADED_MASK; | ||
374 | |||
375 | return oldest; | ||
376 | } | ||
377 | #endif | ||
378 | /* fetch a clip from the voice file */ | ||
379 | static int get_clip(long id, struct queue_entry *q) | ||
380 | { | ||
381 | int index; | ||
382 | int retval = -1; | ||
383 | struct clip_entry* clipbuf; | ||
384 | size_t clipsize; | ||
385 | enum offset_type type; | ||
386 | |||
387 | index = id2index(id); | ||
388 | if (index == -1) | ||
389 | return -1; | ||
334 | 390 | ||
335 | clipbuf = core_get_data(index_handle); | 391 | clipbuf = core_get_data(index_handle); |
336 | clipsize = clipbuf[id].size; | 392 | clipsize = clipbuf[index].size; |
337 | if (clipsize == 0) /* clip not included in voicefile */ | 393 | if (clipsize == 0) /* clip not included in voicefile */ |
338 | return -1; | 394 | return -1; |
339 | 395 | ||
340 | #ifndef TALK_PARTIAL_LOAD | 396 | #ifndef TALK_PARTIAL_LOAD |
341 | retval = clipbuf[id].offset; | 397 | retval = clipbuf[index].offset; |
398 | type = TALK_OFFSET; | ||
342 | 399 | ||
343 | #else | 400 | #else |
344 | if (!(clipsize & LOADED_MASK)) | 401 | if (!(clipsize & LOADED_MASK)) |
345 | { /* clip needs loading */ | 402 | { /* clip needs loading */ |
403 | struct clip_cache_metadata *cc; | ||
404 | int fd, handle, oldest = -1; | ||
405 | unsigned i; | ||
346 | ssize_t ret; | 406 | ssize_t ret; |
347 | int fd, idx = 0; | ||
348 | unsigned char *voicebuf; | ||
349 | cache_misses++; | 407 | cache_misses++; |
350 | if (id == VOICE_PAUSE) { | 408 | /* free clips from cache until this one succeeds to allocate */ |
351 | idx = QUEUE_SIZE; /* we keep VOICE_PAUSE loaded */ | 409 | while ((handle = buflib_alloc(&clip_ctx, clipsize)) < 0) |
352 | } else { | 410 | oldest = free_oldest_clip(); |
353 | int oldest = 0, i; | 411 | /* handle should now hold a valid alloc. Load from disk |
354 | for(i=0; i<QUEUE_SIZE; i++) { | 412 | * and insert into cache */ |
355 | if (buffered_id[i] < 0) { | ||
356 | /* found a free entry, that means the buffer isn't | ||
357 | * full yet. */ | ||
358 | idx = i; | ||
359 | break; | ||
360 | } | ||
361 | |||
362 | /* find the oldest clip */ | ||
363 | if(clip_age[i] > oldest) { | ||
364 | idx = i; | ||
365 | oldest = clip_age[i]; | ||
366 | } | ||
367 | |||
368 | /* increment age of each loaded clip */ | ||
369 | clip_age[i]++; | ||
370 | } | ||
371 | clip_age[idx] = 0; /* reset clip's age */ | ||
372 | } | ||
373 | retval = idx * max_clipsize; | ||
374 | fd = open_voicefile(); | 413 | fd = open_voicefile(); |
375 | if (fd < 0) | 414 | if (fd < 0) |
415 | { | ||
416 | buflib_free(&clip_ctx, handle); | ||
376 | return -1; /* open error */ | 417 | return -1; /* open error */ |
377 | 418 | } | |
378 | talk_handle_locked++; | ||
379 | voicebuf = core_get_data(talk_handle); | ||
380 | clipbuf = core_get_data(index_handle); | 419 | clipbuf = core_get_data(index_handle); |
381 | lseek(fd, clipbuf[id].offset, SEEK_SET); | 420 | lseek(fd, clipbuf[index].offset, SEEK_SET); |
382 | ret = read(fd, &voicebuf[retval], clipsize); | 421 | ret = read_to_handle_ex(fd, &clip_ctx, handle, 0, clipsize); |
383 | close(fd); | 422 | close(fd); |
384 | talk_handle_locked--; | ||
385 | 423 | ||
386 | if (ret < 0 || clipsize != (size_t)ret) | 424 | if (ret < 0 || clipsize != (size_t)ret) |
425 | { | ||
426 | buflib_free(&clip_ctx, handle); | ||
387 | return -1; /* read error */ | 427 | return -1; /* read error */ |
428 | } | ||
388 | 429 | ||
389 | clipbuf = core_get_data(index_handle); | 430 | clipbuf = core_get_data(index_handle); |
390 | clipbuf[id].size |= LOADED_MASK; /* mark as loaded */ | 431 | clipbuf[index].size |= LOADED_MASK; /* mark as loaded */ |
391 | 432 | ||
392 | if (id != VOICE_PAUSE) { | 433 | /* finally insert into metadata table */ |
393 | if (buffered_id[idx] >= 0) { | 434 | cc = buflib_get_data(&clip_ctx, metadata_table_handle); |
394 | /* mark previously loaded clip as unloaded */ | 435 | if (oldest != -1) |
395 | clipbuf[buffered_id[idx]].size &= ~LOADED_MASK; | 436 | /* went through the cache in the above loop already, re-use the slot */ |
396 | } | 437 | cc = &cc[oldest]; |
397 | buffered_id[idx] = id; | 438 | else |
439 | { /* find an empty slot */ | ||
440 | for(i = 0; cc[i].handle && i < max_clips; i++) ; | ||
441 | if (i == max_clips) /* no free slot in the cache table? */ | ||
442 | i = free_oldest_clip(); | ||
443 | cc = &cc[i]; | ||
398 | } | 444 | } |
445 | cc->handle = handle; | ||
446 | cc->tick = current_tick; | ||
447 | cc->voice_id = id; | ||
448 | retval = handle; | ||
399 | } | 449 | } |
400 | else | 450 | else |
401 | { /* clip is in memory already */ | 451 | { /* clip is in memory already; find where it was loaded */ |
402 | /* Find where it was loaded */ | ||
403 | cache_hits++; | 452 | cache_hits++; |
404 | if (id == VOICE_PAUSE) { | 453 | struct clip_cache_metadata *cc; |
405 | retval = QUEUE_SIZE * max_clipsize; | 454 | static int i; |
406 | } else { | 455 | cc = buflib_get_data(&clip_ctx, metadata_table_handle); |
407 | int idx; | 456 | for (i = 0; cc[i].voice_id != id || !cc[i].handle; i++) ; |
408 | for (idx=0; idx<QUEUE_SIZE; idx++) | 457 | cc[i].tick = current_tick; /* reset age */ |
409 | if (buffered_id[idx] == id) { | ||
410 | retval = idx * max_clipsize; | ||
411 | clip_age[idx] = 0; /* reset clip's age */ | ||
412 | break; | ||
413 | } | ||
414 | } | ||
415 | clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */ | 458 | clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */ |
459 | retval = cc[i].handle; | ||
416 | } | 460 | } |
461 | type = TALK_HANDLE; | ||
417 | #endif /* TALK_PARTIAL_LOAD */ | 462 | #endif /* TALK_PARTIAL_LOAD */ |
418 | 463 | ||
419 | *p_size = clipsize; | 464 | q->offset = retval; |
420 | return retval; | 465 | q->length = clipsize; |
466 | q->remaining = clipsize; | ||
467 | q->type = type; | ||
468 | return 0; | ||
421 | } | 469 | } |
422 | 470 | ||
423 | static bool load_index_table(int fd, const struct voicefile_header *hdr) | 471 | static bool load_index_table(int fd, const struct voicefile_header *hdr) |
@@ -429,13 +477,11 @@ static bool load_index_table(int fd, const struct voicefile_header *hdr) | |||
429 | return true; | 477 | return true; |
430 | 478 | ||
431 | ssize_t alloc_size = (hdr->id1_max + hdr->id2_max) * sizeof(struct clip_entry); | 479 | ssize_t alloc_size = (hdr->id1_max + hdr->id2_max) * sizeof(struct clip_entry); |
432 | index_handle = core_alloc_ex("voice index", alloc_size, &index_ops); | 480 | index_handle = core_alloc_ex("voice index", alloc_size, &talk_ops); |
433 | if (index_handle < 0) | 481 | if (index_handle < 0) |
434 | return false; | 482 | return false; |
435 | 483 | ||
436 | index_handle_locked++; | 484 | ret = read_to_handle(fd, index_handle, 0, alloc_size); |
437 | buf = core_get_data(index_handle); | ||
438 | ret = read(fd, buf, alloc_size); | ||
439 | 485 | ||
440 | #ifndef TALK_PARTIAL_LOAD | 486 | #ifndef TALK_PARTIAL_LOAD |
441 | int clips_offset, num_clips; | 487 | int clips_offset, num_clips; |
@@ -447,19 +493,20 @@ static bool load_index_table(int fd, const struct voicefile_header *hdr) | |||
447 | clips_offset += num_clips * sizeof(struct clip_entry); /* skip index */ | 493 | clips_offset += num_clips * sizeof(struct clip_entry); /* skip index */ |
448 | #endif | 494 | #endif |
449 | if (ret == alloc_size) | 495 | if (ret == alloc_size) |
496 | { | ||
497 | buf = core_get_data(index_handle); | ||
450 | for (int i = 0; i < hdr->id1_max + hdr->id2_max; i++) | 498 | for (int i = 0; i < hdr->id1_max + hdr->id2_max; i++) |
451 | { | 499 | { |
452 | #ifdef ROCKBOX_LITTLE_ENDIAN | 500 | #ifdef ROCKBOX_LITTLE_ENDIAN |
501 | /* doesn't yield() */ | ||
453 | structec_convert(&buf[i], "ll", 1, true); | 502 | structec_convert(&buf[i], "ll", 1, true); |
454 | #endif | 503 | #endif |
455 | #ifndef TALK_PARTIAL_LOAD | 504 | #ifndef TALK_PARTIAL_LOAD |
456 | buf[i].offset -= clips_offset; | 505 | buf[i].offset -= clips_offset; |
457 | #endif | 506 | #endif |
458 | } | 507 | } |
459 | 508 | } | |
460 | index_handle_locked--; | 509 | else |
461 | |||
462 | if (ret != alloc_size) | ||
463 | index_handle = core_free(index_handle); | 510 | index_handle = core_free(index_handle); |
464 | 511 | ||
465 | return ret == alloc_size; | 512 | return ret == alloc_size; |
@@ -481,20 +528,16 @@ static bool load_header(int fd, struct voicefile_header *hdr) | |||
481 | #ifndef TALK_PARTIAL_LOAD | 528 | #ifndef TALK_PARTIAL_LOAD |
482 | static bool load_data(int fd, ssize_t size_to_read) | 529 | static bool load_data(int fd, ssize_t size_to_read) |
483 | { | 530 | { |
484 | unsigned char *buf; | ||
485 | ssize_t ret; | 531 | ssize_t ret; |
486 | 532 | ||
487 | if (size_to_read < 0) | 533 | if (size_to_read < 0) |
488 | return false; | 534 | return false; |
489 | 535 | ||
490 | talk_handle = core_alloc_ex("voice data", size_to_read, &clip_ops); | 536 | talk_handle = core_alloc_ex("voice data", size_to_read, &talk_ops); |
491 | if (talk_handle < 0) | 537 | if (talk_handle < 0) |
492 | return false; | 538 | return false; |
493 | 539 | ||
494 | talk_handle_locked++; | 540 | ret = read_to_handle(fd, talk_handle, 0, size_to_read); |
495 | buf = core_get_data(talk_handle); | ||
496 | ret = read(fd, buf, size_to_read); | ||
497 | talk_handle_locked--; | ||
498 | 541 | ||
499 | if (ret != size_to_read) | 542 | if (ret != size_to_read) |
500 | talk_handle = core_free(talk_handle); | 543 | talk_handle = core_free(talk_handle); |
@@ -558,23 +601,33 @@ static bool load_voicefile_data(int fd, size_t max_size) | |||
558 | { | 601 | { |
559 | #ifdef TALK_PARTIAL_LOAD | 602 | #ifdef TALK_PARTIAL_LOAD |
560 | (void)fd; | 603 | (void)fd; |
561 | /* just allocate, populate on an as-needed basis later */ | 604 | size_t alloc_size; |
562 | talk_handle = core_alloc_ex("voice data", max_size, &clip_ops); | 605 | /* just allocate, populate on an as-needed basis later |
606 | * re-create the clip buffer to ensure clip_ctx is up-to-date */ | ||
607 | if (talk_handle > 0) | ||
608 | talk_handle = core_free(talk_handle); | ||
609 | talk_handle = core_alloc_ex("voice data", max_size, &talk_ops); | ||
563 | if (talk_handle < 0) | 610 | if (talk_handle < 0) |
564 | goto load_err_free; | 611 | goto load_err_free; |
612 | |||
613 | buflib_init(&clip_ctx, core_get_data(talk_handle), max_size); | ||
614 | |||
615 | alloc_size = max_clips * sizeof(struct clip_cache_metadata); | ||
616 | /* the first alloc is the clip metadata table */ | ||
617 | metadata_table_handle = buflib_alloc(&clip_ctx, alloc_size); | ||
618 | memset(buflib_get_data(&clip_ctx, metadata_table_handle), 0, alloc_size); | ||
619 | |||
565 | #else | 620 | #else |
566 | size_t load_size, clips_size; | ||
567 | /* load the entire file into memory */ | 621 | /* load the entire file into memory */ |
568 | clips_size = (voicefile.id1_max+voicefile.id2_max) * sizeof(struct clip_entry); | 622 | if (!load_data(fd, max_size)) |
569 | load_size = max_size - voicefile.table - clips_size; | ||
570 | if (!load_data(fd, load_size)) | ||
571 | goto load_err_free; | 623 | goto load_err_free; |
572 | #endif | 624 | #endif |
573 | 625 | ||
574 | /* make sure to have the silence clip, if available | 626 | /* make sure to have the silence clip, if available |
575 | * return value can be cached globally even for TALK_PARTIAL_LOAD because | 627 | * return value can be cached globally even for TALK_PARTIAL_LOAD because |
576 | * the VOICE_PAUSE clip is specially handled */ | 628 | * the VOICE_PAUSE clip is specially handled */ |
577 | silence_offset = get_clip(VOICE_PAUSE, &silence_length); | 629 | if (get_clip(VOICE_PAUSE, &silence)) |
630 | goto load_err_free; | ||
578 | 631 | ||
579 | /* not an error if this fails here, might try again when the | 632 | /* not an error if this fails here, might try again when the |
580 | * actual thumbnails are attempted to be played back */ | 633 | * actual thumbnails are attempted to be played back */ |
@@ -587,74 +640,94 @@ load_err_free: | |||
587 | return false; | 640 | return false; |
588 | } | 641 | } |
589 | 642 | ||
643 | /* most, if not all, clips should be well below 32k (largest in english.lang is | ||
644 | * 4.5K). Currently there is a problem with voice decoding such that clips | ||
645 | * cannot be decoded in chunks. Once that is resolved this buffer could be | ||
646 | * smaller and clips be decoded in multiple chunks */ | ||
647 | static unsigned char commit_buffer[32<<10]; | ||
648 | |||
649 | static void* commit_transfer(struct queue_entry *qe, size_t *size) | ||
650 | { | ||
651 | void *buf = NULL; /* shut up gcc */ | ||
652 | static unsigned char *bufpos = commit_buffer; | ||
653 | int offset = qe->offset; | ||
654 | #if CONFIG_CODEC != SWCODEC | ||
655 | sent = MIN(qe->remaining, 0xFFFF); | ||
656 | #else | ||
657 | sent = qe->remaining; | ||
658 | #endif | ||
659 | sent = MIN((size_t)sent, sizeof(commit_buffer)); | ||
660 | switch (qe->type) | ||
661 | { | ||
662 | case TALK_OFFSET: buf = core_get_data(talk_handle) + offset; break; | ||
663 | case THUMB_OFFSET: buf = core_get_data(thumb_handle) + offset; break; | ||
664 | #ifdef TALK_PARTIAL_LOAD | ||
665 | case TALK_HANDLE: buf = buflib_get_data(&clip_ctx, offset); break; | ||
666 | #endif | ||
667 | } | ||
668 | /* adjust buffer position to what has been played already */ | ||
669 | buf += (qe->length - qe->remaining); | ||
670 | memcpy(bufpos, buf, sent); | ||
671 | *size = sent; | ||
672 | |||
673 | |||
674 | return commit_buffer; | ||
675 | } | ||
676 | |||
677 | static inline bool is_silence(struct queue_entry *qe) | ||
678 | { | ||
679 | if (silence.length > 0) /* silence clip available? */ | ||
680 | return (qe->offset == silence.offset && qe->type == silence.type); | ||
681 | else | ||
682 | return false; | ||
683 | } | ||
684 | |||
590 | /* called in ISR context (on HWCODEC) if mp3 data got consumed */ | 685 | /* called in ISR context (on HWCODEC) if mp3 data got consumed */ |
591 | static void mp3_callback(const void** start, size_t* size) | 686 | static void mp3_callback(const void** start, size_t* size) |
592 | { | 687 | { |
593 | queue[queue_read].length -= sent; /* we completed this */ | 688 | struct queue_entry *qe = &queue[queue_read]; |
594 | queue[queue_read].offset += sent; | 689 | qe->remaining -= sent; /* we completed this */ |
595 | 690 | ||
596 | if (queue[queue_read].length > 0) /* current clip not finished? */ | 691 | if (qe->remaining > 0) /* current clip not finished? */ |
597 | { /* feed the next 64K-1 chunk */ | 692 | { /* feed the next 64K-1 chunk */ |
598 | int offset; | 693 | *start = commit_transfer(qe, size); |
599 | #if CONFIG_CODEC != SWCODEC | ||
600 | sent = MIN(queue[queue_read].length, 0xFFFF); | ||
601 | #else | ||
602 | sent = queue[queue_read].length; | ||
603 | #endif | ||
604 | offset = queue[queue_read].offset; | ||
605 | if ((unsigned long)offset >= voicefile_size) | ||
606 | *start = core_get_data(thumb_handle) + offset - voicefile_size; | ||
607 | else | ||
608 | *start = core_get_data(talk_handle) + offset; | ||
609 | *size = sent; | ||
610 | return; | 694 | return; |
611 | } | 695 | } |
696 | |||
612 | talk_queue_lock(); | 697 | talk_queue_lock(); |
613 | if(thumb_handle && (unsigned long)queue[queue_read].offset == voicefile_size+thumbnail_buf_used) | 698 | /* check if thumbnails have been played */ |
614 | thumbnail_buf_used = 0; | 699 | if (qe->type == THUMB_OFFSET) |
615 | if (sent > 0) /* go to next entry */ | ||
616 | { | 700 | { |
617 | queue_read = (queue_read + 1) & QUEUE_MASK; | 701 | if (qe->remaining == 0 && (qe->length + qe->offset) == thumbnail_buf_used) |
702 | thumbnail_buf_used = 0; | ||
618 | } | 703 | } |
619 | 704 | ||
620 | re_check: | 705 | /* increment read position for the just played clip */ |
706 | queue_read = (queue_read + 1) & QUEUE_MASK; | ||
621 | 707 | ||
622 | if (QUEUE_LEVEL != 0) /* queue is not empty? */ | 708 | if (QUEUE_LEVEL == 0) |
623 | { /* start next clip */ | 709 | { |
624 | unsigned char *buf; | 710 | if (!is_silence(last_clip) && last_clip->type != THUMB_OFFSET) |
625 | #if CONFIG_CODEC != SWCODEC | 711 | { /* add silence clip when queue runs empty playing a voice clip, |
626 | sent = MIN(queue[queue_read].length, 0xFFFF); | 712 | * only if the previous clip wasn't silence or thumbnail */ |
627 | #else | 713 | queue[queue_write] = silence; |
628 | sent = queue[queue_read].length; | 714 | queue_write = (queue_write + 1) & QUEUE_MASK; |
629 | #endif | 715 | } |
630 | lastclip_offset = queue[queue_read].offset; | ||
631 | /* offsets larger than voicefile_size denote thumbnail clips */ | ||
632 | if (lastclip_offset >= voicefile_size) | ||
633 | buf = core_get_data(thumb_handle) + lastclip_offset - voicefile_size; | ||
634 | else | 716 | else |
635 | buf = core_get_data(talk_handle) + lastclip_offset; | 717 | { |
636 | *start = buf; | 718 | *size = 0; /* end of data */ |
637 | *size = sent; | 719 | } |
638 | curr_hd[0] = buf[1]; | ||
639 | curr_hd[1] = buf[2]; | ||
640 | curr_hd[2] = buf[3]; | ||
641 | } | 720 | } |
642 | else if (silence_offset > 0 /* silence clip available */ | ||
643 | && lastclip_offset != (unsigned long)silence_offset /* previous clip wasn't silence */ | ||
644 | && !(lastclip_offset >= voicefile_size /* ..or thumbnail */ | ||
645 | && lastclip_offset < voicefile_size +size_for_thumbnail)) | ||
646 | { /* add silence clip when queue runs empty playing a voice clip */ | ||
647 | queue[queue_write].offset = silence_offset; | ||
648 | queue[queue_write].length = silence_length; | ||
649 | queue_write = (queue_write + 1) & QUEUE_MASK; | ||
650 | 721 | ||
651 | goto re_check; | 722 | if (QUEUE_LEVEL != 0) /* queue is not empty? */ |
652 | } | 723 | { /* start next clip */ |
653 | else | 724 | last_clip = &queue[queue_read]; |
654 | { | 725 | *start = commit_transfer(last_clip, size); |
655 | *size = 0; /* end of data */ | 726 | curr_hd[0] = commit_buffer[1]; |
656 | talk_handle_locked--; | 727 | curr_hd[1] = commit_buffer[2]; |
728 | curr_hd[2] = commit_buffer[3]; | ||
657 | } | 729 | } |
730 | |||
658 | talk_queue_unlock(); | 731 | talk_queue_unlock(); |
659 | } | 732 | } |
660 | 733 | ||
@@ -672,7 +745,7 @@ void talk_force_shutup(void) | |||
672 | unsigned char* search; | 745 | unsigned char* search; |
673 | unsigned char* end; | 746 | unsigned char* end; |
674 | int len; | 747 | int len; |
675 | unsigned clip_offset; | 748 | unsigned offset; |
676 | if (QUEUE_LEVEL == 0) /* has ended anyway */ | 749 | if (QUEUE_LEVEL == 0) /* has ended anyway */ |
677 | return; | 750 | return; |
678 | 751 | ||
@@ -681,11 +754,16 @@ void talk_force_shutup(void) | |||
681 | #endif /* CONFIG_CPU == SH7034 */ | 754 | #endif /* CONFIG_CPU == SH7034 */ |
682 | /* search next frame boundary and continue up to there */ | 755 | /* search next frame boundary and continue up to there */ |
683 | pos = search = mp3_get_pos(); | 756 | pos = search = mp3_get_pos(); |
684 | clip_offset = queue[queue_read].offset; | 757 | offset = queue[queue_read].offset; |
685 | if (clip_offset >= voicefile_size) | 758 | switch (queue[queue_read].type) |
686 | end = core_get_data(thumb_handle) + clip_offset - voicefile_size; | 759 | { |
687 | else | 760 | case TALK_OFFSET: end = core_get_data(talk_handle) + offset; break; |
688 | end = core_get_data(talk_handle) + clip_offset; | 761 | case THUMB_OFFSET: end = core_get_data(thumb_handle) + offset; break; |
762 | #ifdef TALK_PARTIAL_LOAD | ||
763 | case TALK_HANDLE: end = buflib_get_data(&clip_ctx, offset); break; | ||
764 | #endif | ||
765 | default: end = NULL; /* shut up gcc */ | ||
766 | } | ||
689 | len = queue[queue_read].length; | 767 | len = queue[queue_read].length; |
690 | 768 | ||
691 | if (pos >= end && pos <= (end+len)) /* really our clip? */ | 769 | if (pos >= end && pos <= (end+len)) /* really our clip? */ |
@@ -727,7 +805,6 @@ void talk_force_shutup(void) | |||
727 | mp3_play_stop(); | 805 | mp3_play_stop(); |
728 | talk_queue_lock(); | 806 | talk_queue_lock(); |
729 | queue_write = queue_read = 0; /* reset the queue */ | 807 | queue_write = queue_read = 0; /* reset the queue */ |
730 | talk_handle_locked = MAX(talk_handle_locked-1, 0); | ||
731 | thumbnail_buf_used = 0; | 808 | thumbnail_buf_used = 0; |
732 | talk_queue_unlock(); | 809 | talk_queue_unlock(); |
733 | need_shutup = false; | 810 | need_shutup = false; |
@@ -741,9 +818,9 @@ void talk_shutup(void) | |||
741 | } | 818 | } |
742 | 819 | ||
743 | /* schedule a clip, at the end or discard the existing queue */ | 820 | /* schedule a clip, at the end or discard the existing queue */ |
744 | static void queue_clip(unsigned long clip_offset, long size, bool enqueue) | 821 | static void queue_clip(struct queue_entry *clip, bool enqueue) |
745 | { | 822 | { |
746 | unsigned char *buf; | 823 | struct queue_entry *qe; |
747 | int queue_level; | 824 | int queue_level; |
748 | 825 | ||
749 | if (!enqueue) | 826 | if (!enqueue) |
@@ -752,7 +829,7 @@ static void queue_clip(unsigned long clip_offset, long size, bool enqueue) | |||
752 | longer in effect. */ | 829 | longer in effect. */ |
753 | force_enqueue_next = false; | 830 | force_enqueue_next = false; |
754 | 831 | ||
755 | if (!size) | 832 | if (!clip->length) |
756 | return; /* safety check */ | 833 | return; /* safety check */ |
757 | #if CONFIG_CPU == SH7034 | 834 | #if CONFIG_CPU == SH7034 |
758 | /* disable the DMA temporarily, to be safe of race condition */ | 835 | /* disable the DMA temporarily, to be safe of race condition */ |
@@ -760,32 +837,24 @@ static void queue_clip(unsigned long clip_offset, long size, bool enqueue) | |||
760 | #endif | 837 | #endif |
761 | talk_queue_lock(); | 838 | talk_queue_lock(); |
762 | queue_level = QUEUE_LEVEL; /* check old level */ | 839 | queue_level = QUEUE_LEVEL; /* check old level */ |
840 | qe = &queue[queue_write]; | ||
763 | 841 | ||
764 | if (queue_level < QUEUE_SIZE - 1) /* space left? */ | 842 | if (queue_level < QUEUE_SIZE - 1) /* space left? */ |
765 | { | 843 | { |
766 | queue[queue_write].offset = clip_offset; /* populate an entry */ | 844 | queue[queue_write] = *clip; |
767 | queue[queue_write].length = size; | ||
768 | queue_write = (queue_write + 1) & QUEUE_MASK; | 845 | queue_write = (queue_write + 1) & QUEUE_MASK; |
769 | } | 846 | } |
770 | talk_queue_unlock(); | 847 | talk_queue_unlock(); |
771 | 848 | ||
772 | if (queue_level == 0) | 849 | if (queue_level == 0) |
773 | { /* queue was empty, we have to do the initial start */ | 850 | { /* queue was empty, we have to do the initial start */ |
774 | lastclip_offset = clip_offset; | 851 | size_t size; |
775 | #if CONFIG_CODEC != SWCODEC | 852 | void *buf = commit_transfer(qe, &size); |
776 | sent = MIN(size, 0xFFFF); /* DMA can do no more */ | 853 | last_clip = qe; |
777 | #else | 854 | mp3_play_data(buf, size, mp3_callback); |
778 | sent = size; | 855 | curr_hd[0] = commit_buffer[1]; |
779 | #endif | 856 | curr_hd[1] = commit_buffer[2]; |
780 | talk_handle_locked++; | 857 | curr_hd[2] = commit_buffer[3]; |
781 | if (clip_offset >= voicefile_size) | ||
782 | buf = core_get_data(thumb_handle) + clip_offset - voicefile_size; | ||
783 | else | ||
784 | buf = core_get_data(talk_handle) + clip_offset; | ||
785 | mp3_play_data(buf, sent, mp3_callback); | ||
786 | curr_hd[0] = buf[1]; | ||
787 | curr_hd[1] = buf[2]; | ||
788 | curr_hd[2] = buf[3]; | ||
789 | mp3_play_pause(true); /* kickoff audio */ | 858 | mp3_play_pause(true); /* kickoff audio */ |
790 | } | 859 | } |
791 | else | 860 | else |
@@ -822,11 +891,13 @@ void talk_init(void) | |||
822 | return; | 891 | return; |
823 | } | 892 | } |
824 | 893 | ||
825 | #if CONFIG_CODEC == SWCODEC | ||
826 | if(!talk_initialized) | 894 | if(!talk_initialized) |
895 | { | ||
896 | #if CONFIG_CODEC == SWCODEC | ||
827 | mutex_init(&queue_mutex); | 897 | mutex_init(&queue_mutex); |
828 | #endif /* CONFIG_CODEC == SWCODEC */ | 898 | #endif /* CONFIG_CODEC == SWCODEC */ |
829 | 899 | mutex_init(&read_buffer_mutex); | |
900 | } | ||
830 | talk_initialized = true; | 901 | talk_initialized = true; |
831 | strlcpy((char *)last_lang, (char *)global_settings.lang_file, | 902 | strlcpy((char *)last_lang, (char *)global_settings.lang_file, |
832 | MAX_FILENAME); | 903 | MAX_FILENAME); |
@@ -835,12 +906,7 @@ void talk_init(void) | |||
835 | queue_write = queue_read = 0; /* reset the queue */ | 906 | queue_write = queue_read = 0; /* reset the queue */ |
836 | memset(&voicefile, 0, sizeof(voicefile)); | 907 | memset(&voicefile, 0, sizeof(voicefile)); |
837 | 908 | ||
838 | #ifdef TALK_PARTIAL_LOAD | 909 | silence.offset = -1; /* pause clip not accessible */ |
839 | for(int i=0; i<QUEUE_SIZE; i++) | ||
840 | buffered_id[i] = -1; | ||
841 | #endif | ||
842 | |||
843 | silence_offset = -1; /* pause clip not accessible */ | ||
844 | voicefile_size = has_voicefile = 0; | 910 | voicefile_size = has_voicefile = 0; |
845 | /* need to free these as their size depends on the voice file, and | 911 | /* need to free these as their size depends on the voice file, and |
846 | * this function is called when the talk voice file changes */ | 912 | * this function is called when the talk voice file changes */ |
@@ -861,34 +927,59 @@ void talk_init(void) | |||
861 | * at once */ | 927 | * at once */ |
862 | unsigned num_clips = voicefile.id1_max + voicefile.id2_max; | 928 | unsigned num_clips = voicefile.id1_max + voicefile.id2_max; |
863 | struct clip_entry *clips = core_get_data(index_handle); | 929 | struct clip_entry *clips = core_get_data(index_handle); |
864 | int silence_size = 0; | 930 | int avg_size = clips[0].size; |
865 | 931 | int real_clips = 1; /* shut up gcc */ | |
866 | for(unsigned i=0; i<num_clips; i++) { | 932 | /* check for the smallest clip size to estimate the max. number of clips |
867 | int size = clips[i].size; | 933 | * the buffer has to hold */ |
868 | if (size > max_clipsize) | 934 | for(unsigned i=1; i<num_clips; i++) |
869 | max_clipsize = size; | 935 | { |
870 | if (i == VOICE_PAUSE) | 936 | if (clips[i].size) |
871 | silence_size = size; | 937 | { /* don't consider empty clips, they are not stored anyway */ |
938 | real_clips += 1; | ||
939 | avg_size += clips[i].size; | ||
940 | } | ||
872 | } | 941 | } |
873 | 942 | avg_size /= real_clips; | |
874 | voicefile_size = voicefile.table + num_clips * sizeof(struct clip_entry); | 943 | |
875 | voicefile_size += max_clipsize * QUEUE_SIZE + silence_size; | 944 | max_clips = MIN((int)(MAX_CLIP_BUFFER_SIZE/avg_size) + 1, real_clips); |
876 | 945 | voicefile_size = MAX_CLIP_BUFFER_SIZE; | |
877 | /* test if we can open and if it fits in the audiobuffer */ | 946 | /* additionally to the clip we need a table to record the age of the clips |
878 | size_t audiobufsz = audio_buffer_available(); | 947 | * so that, when memory is tight, only the most recently used ones are kept */ |
879 | has_voicefile = audiobufsz >= voicefile_size; | 948 | voicefile_size += sizeof(struct clip_cache_metadata) * max_clips; |
949 | /* compensate a bit for buflib per-alloc overhead */ | ||
950 | voicefile_size += BUFLIB_ALLOC_OVERHEAD * max_clips; | ||
951 | /* cap to the max. number of clips or the size of the available audio | ||
952 | * buffer which we grab. We leave some to the rest of the system. | ||
953 | * While that reduces our buffer size it improves the chance that | ||
954 | * other allocs succeed without disabling voice which would require | ||
955 | * reloading the voice from disk (as we do not shrink our buffer when | ||
956 | * other code attempts new allocs these would fail) */ | ||
957 | ssize_t cap = MIN(MAX_CLIP_BUFFER_SIZE, audio_buffer_available() - (64<<10)); | ||
958 | if (UNLIKELY(cap < 0)) | ||
959 | { | ||
960 | logf("Not enough memory for voice. Disabling...\n"); | ||
961 | if (index_handle > 0) | ||
962 | index_handle = core_free(index_handle); | ||
963 | voicefile_size = 0; | ||
964 | goto out; | ||
965 | } | ||
966 | else if (voicefile_size > (size_t)cap) | ||
967 | voicefile_size = cap; | ||
880 | 968 | ||
881 | #else | 969 | #else |
882 | /* load the compressed clip data into memory, in its entirety */ | 970 | size_t clips_size; |
883 | voicefile_size = filesize(filehandle); | 971 | clips_size = (voicefile.id1_max+voicefile.id2_max) * sizeof(struct clip_entry); |
972 | voicefile_size = filesize(filehandle) - voicefile.table - clips_size; | ||
973 | /* load the compressed clip data into memory */ | ||
884 | if (!load_voicefile_data(filehandle, voicefile_size)) | 974 | if (!load_voicefile_data(filehandle, voicefile_size)) |
885 | { | 975 | { |
886 | voicefile_size = 0; | 976 | voicefile_size = 0; |
887 | goto out; | 977 | goto out; |
888 | } | 978 | } |
889 | has_voicefile = true; | ||
890 | #endif | 979 | #endif |
891 | 980 | ||
981 | has_voicefile = true; | ||
982 | |||
892 | #if CONFIG_CODEC == SWCODEC | 983 | #if CONFIG_CODEC == SWCODEC |
893 | /* Initialize the actual voice clip playback engine as well */ | 984 | /* Initialize the actual voice clip playback engine as well */ |
894 | if (talk_voice_required()) | 985 | if (talk_voice_required()) |
@@ -915,10 +1006,9 @@ void talk_buffer_set_policy(int policy) | |||
915 | /* play a voice ID from voicefile */ | 1006 | /* play a voice ID from voicefile */ |
916 | int talk_id(int32_t id, bool enqueue) | 1007 | int talk_id(int32_t id, bool enqueue) |
917 | { | 1008 | { |
918 | int clip; | ||
919 | long clipsize; | ||
920 | int32_t unit; | 1009 | int32_t unit; |
921 | int decimals; | 1010 | int decimals; |
1011 | struct queue_entry clip; | ||
922 | 1012 | ||
923 | if (!has_voicefile) | 1013 | if (!has_voicefile) |
924 | return 0; /* no voicefile loaded, not an error -> pretent success */ | 1014 | return 0; /* no voicefile loaded, not an error -> pretent success */ |
@@ -952,8 +1042,7 @@ int talk_id(int32_t id, bool enqueue) | |||
952 | return 0; /* and stop, end of special case */ | 1042 | return 0; /* and stop, end of special case */ |
953 | } | 1043 | } |
954 | 1044 | ||
955 | clip = get_clip(id, &clipsize); | 1045 | if (get_clip(id, &clip) < 0) |
956 | if (clip < 0) | ||
957 | return -1; /* not present */ | 1046 | return -1; /* not present */ |
958 | 1047 | ||
959 | #ifdef LOGF_ENABLE | 1048 | #ifdef LOGF_ENABLE |
@@ -963,7 +1052,7 @@ int talk_id(int32_t id, bool enqueue) | |||
963 | logf("\ntalk_id: Say '%s'\n", str(id)); | 1052 | logf("\ntalk_id: Say '%s'\n", str(id)); |
964 | #endif | 1053 | #endif |
965 | 1054 | ||
966 | queue_clip(clip, clipsize, enqueue); | 1055 | queue_clip(&clip, enqueue); |
967 | 1056 | ||
968 | return 0; | 1057 | return 0; |
969 | } | 1058 | } |
@@ -997,11 +1086,11 @@ static int _talk_file(const char* filename, | |||
997 | int fd; | 1086 | int fd; |
998 | int size; | 1087 | int size; |
999 | int thumb_used; | 1088 | int thumb_used; |
1000 | char *buf; | ||
1001 | #if CONFIG_CODEC != SWCODEC | 1089 | #if CONFIG_CODEC != SWCODEC |
1002 | struct mp3entry info; | 1090 | struct mp3entry info; |
1003 | #endif | 1091 | #endif |
1004 | 1092 | ||
1093 | /* reload needed? */ | ||
1005 | if (talk_temp_disable_count > 0) | 1094 | if (talk_temp_disable_count > 0) |
1006 | return -1; /* talking has been disabled */ | 1095 | return -1; /* talking has been disabled */ |
1007 | if (!check_audio_status()) | 1096 | if (!check_audio_status()) |
@@ -1038,16 +1127,14 @@ static int _talk_file(const char* filename, | |||
1038 | lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ | 1127 | lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ |
1039 | #endif | 1128 | #endif |
1040 | 1129 | ||
1041 | talk_handle_locked++; | 1130 | size = read_to_handle(fd, thumb_handle, thumb_used, size_for_thumbnail - thumb_used); |
1042 | buf = core_get_data(thumb_handle); | ||
1043 | size = read(fd, buf+thumb_used, size_for_thumbnail - thumb_used); | ||
1044 | talk_handle_locked--; | ||
1045 | close(fd); | 1131 | close(fd); |
1046 | 1132 | ||
1047 | /* ToDo: find audio, skip ID headers and trailers */ | 1133 | /* ToDo: find audio, skip ID headers and trailers */ |
1048 | 1134 | ||
1049 | if (size > 0) /* Don't play missing clips */ | 1135 | if (size > 0) /* Don't play missing clips */ |
1050 | { | 1136 | { |
1137 | struct queue_entry clip; | ||
1051 | #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) | 1138 | #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) |
1052 | /* bitswap doesnt yield() */ | 1139 | /* bitswap doesnt yield() */ |
1053 | bitswap(core_get_data(thumb_handle), size); | 1140 | bitswap(core_get_data(thumb_handle), size); |
@@ -1060,7 +1147,8 @@ static int _talk_file(const char* filename, | |||
1060 | talk_queue_lock(); | 1147 | talk_queue_lock(); |
1061 | thumbnail_buf_used = thumb_used + size; | 1148 | thumbnail_buf_used = thumb_used + size; |
1062 | talk_queue_unlock(); | 1149 | talk_queue_unlock(); |
1063 | queue_clip(voicefile_size + thumb_used, size, true); | 1150 | clip = (struct queue_entry){ .offset = thumb_used, .length = size, .remaining = size, .type = THUMB_OFFSET }; |
1151 | queue_clip(&clip, true); | ||
1064 | } | 1152 | } |
1065 | 1153 | ||
1066 | return size; | 1154 | return size; |
@@ -1460,7 +1548,6 @@ void talk_time(const struct tm *tm, bool enqueue) | |||
1460 | 1548 | ||
1461 | #endif /* CONFIG_RTC */ | 1549 | #endif /* CONFIG_RTC */ |
1462 | 1550 | ||
1463 | |||
1464 | bool talk_get_debug_data(struct talk_debug_data *data) | 1551 | bool talk_get_debug_data(struct talk_debug_data *data) |
1465 | { | 1552 | { |
1466 | char* p_lang = DEFAULT_VOICE_LANG; /* default */ | 1553 | char* p_lang = DEFAULT_VOICE_LANG; /* default */ |
@@ -1500,9 +1587,13 @@ bool talk_get_debug_data(struct talk_debug_data *data) | |||
1500 | } | 1587 | } |
1501 | data->avg_clipsize /= real_clips; | 1588 | data->avg_clipsize /= real_clips; |
1502 | data->num_empty_clips = data->num_clips - real_clips; | 1589 | data->num_empty_clips = data->num_clips - real_clips; |
1503 | data->memory_allocated = voicefile_size + size_for_thumbnail; | 1590 | data->memory_allocated = sizeof(commit_buffer) + sizeof(voicefile) |
1504 | data->memory_used = voicefile_size + thumbnail_buf_used; | 1591 | + data->num_clips * sizeof(struct clip_entry) |
1592 | + voicefile_size + size_for_thumbnail; | ||
1593 | data->memory_used = data->memory_allocated - size_for_thumbnail + thumbnail_buf_used; | ||
1505 | #ifdef TALK_PARTIAL_LOAD | 1594 | #ifdef TALK_PARTIAL_LOAD |
1595 | if (talk_handle > 0) | ||
1596 | data->memory_used -= buflib_available(&clip_ctx); | ||
1506 | data->cached_clips = cached; | 1597 | data->cached_clips = cached; |
1507 | data->cache_hits = cache_hits; | 1598 | data->cache_hits = cache_hits; |
1508 | data->cache_misses = cache_misses; | 1599 | data->cache_misses = cache_misses; |