diff --git a/apps/talk.c b/apps/talk.c index 820cb75c8d..a49de09e84 100644 --- a/apps/talk.c +++ b/apps/talk.c @@ -80,14 +80,6 @@ const char* const file_thumbnail_ext = ".talk"; #define LOADED_MASK 0x80000000 /* MSB */ -/* swcodec: cap thumbnail buffer to MAX_THUMNAIL_BUFSIZE since audio keeps - * playing while voice - * hwcodec: just use whatever is left in the audiobuffer, music - * playback is impossible => no cap */ -#if CONFIG_CODEC == SWCODEC -#define MAX_THUMBNAIL_BUFSIZE 0x10000 -#endif - #define DEFAULT_VOICE_LANG "english" /***************** Data types *****************/ @@ -124,13 +116,14 @@ struct voicefile_header /* file format of our voice file */ #endif #ifndef MAX_CLIP_BUFFER_SIZE -#define MAX_CLIP_BUFFER_SIZE (1<<30) /* 1GB should be enough for everybody */ +/* 1GB should be enough for everybody; will be capped to voicefile size */ +#define MAX_CLIP_BUFFER_SIZE (1<<30) #endif +#define THUMBNAIL_RESERVE (50000) /* Multiple thumbnails can be loaded back-to-back in this buffer. */ static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in thumbnail buffer */ -static long size_for_thumbnail; /* total thumbnail buffer size */ static struct voicefile_header voicefile; /* loaded voicefile */ static bool has_voicefile; /* a voicefile file is present */ static bool need_shutup; /* is there possibly any voice playing to be shutup */ @@ -158,16 +151,9 @@ static unsigned long voicefile_size, voicebuf_size; struct queue_entry /* one entry of the internal queue */ { - int offset; /* actually a buflib handle if type == HANDLE */ - int length; /* total length of the clip */ + int handle; /* buflib handle to the clip data */ + int length; /* total length of the clip */ int remaining; /* bytes that still need to be deoded */ - /* for large QUEUE_SIZE values it might be worthwhile to merge the type - * into the bits of the above members (as a space saver). For small values - * the required extra code outweights this so it's not done here */ - enum offset_type { - TALK_HANDLE, - THUMB_OFFSET, - } type; }; static struct buflib_context clip_ctx; @@ -186,7 +172,7 @@ static struct queue_entry silence, *last_clip; /***************** Private implementation *****************/ -static int index_handle, talk_handle, thumb_handle; +static int index_handle, talk_handle; static int move_callback(int handle, void *current, void *new) { @@ -282,34 +268,12 @@ static int shrink_callback(int handle, unsigned hints, void *start, size_t old_s return BUFLIB_CB_CANNOT_SHRINK; } -static int thumb_shrink_callback(int handle, unsigned hints, void *start, size_t old_size) -{ - (void)start;(void)old_size;(void)hints; - - if (handle == thumb_handle && thumbnail_buf_used == 0) - { - mutex_lock(&read_buffer_mutex); - thumb_handle = core_free(handle); - mutex_unlock(&read_buffer_mutex); - return BUFLIB_CB_OK; - } - - return BUFLIB_CB_CANNOT_SHRINK; -} - - - static struct buflib_callbacks talk_ops = { .move_callback = move_callback, .sync_callback = sync_callback, .shrink_callback = shrink_callback, }; -static struct buflib_callbacks thumb_ops = { - .move_callback = move_callback, - .sync_callback = sync_callback, - .shrink_callback = thumb_shrink_callback, -}; static int open_voicefile(void) { @@ -375,19 +339,31 @@ static int free_oldest_clip(void) struct clip_cache_metadata *cc = buflib_get_data(&clip_ctx, metadata_table_handle); for(age = i = 0, now = current_tick; i < max_clips; i++) { - if (cc[i].handle && (now - cc[i].tick) > age - && cc[i].voice_id != VOICE_PAUSE) /* never consider silence */ + if (cc[i].handle) { - age = now - cc[i].tick; - oldest = i; + if ((now - cc[i].tick) > age && cc[i].voice_id != VOICE_PAUSE) + { + /* find the last-used clip but never consider silence */ + age = now - cc[i].tick; + oldest = i; + } + else if (cc[i].voice_id == VOICEONLY_DELIMITER) + { + /* thumb clips are freed immediately */ + oldest = i; + break; + } } } + /* free the last one if no oldest one could be determined */ cc = &cc[oldest]; cc->handle = buflib_free(&clip_ctx, cc->handle); - /* need to clear the LOADED bit too */ - clipbuf = core_get_data(index_handle); - clipbuf[id2index(cc->voice_id)].size &= ~LOADED_MASK; - + /* need to clear the LOADED bit too (not for thumb clips) */ + if (cc->voice_id != VOICEONLY_DELIMITER) + { + clipbuf = core_get_data(index_handle); + clipbuf[id2index(cc->voice_id)].size &= ~LOADED_MASK; + } return oldest; } @@ -458,6 +434,8 @@ static void load_initial_clips(int fd) int handle; struct clip_entry* clipbuf = core_get_data(index_handle); size_t clipsize = clipbuf[index].size; + ssize_t ret; + if (clipsize == 0) /* clip not included in voicefile */ continue; @@ -465,7 +443,8 @@ static void load_initial_clips(int fd) if (handle < 0) break; - if (read_clip_data(fd, index, handle) < 0) + ret = read_clip_data(fd, index, handle); + if (ret < 0) break; add_cache_entry(handle, i++, index2id(index)); @@ -480,7 +459,6 @@ static int get_clip(long id, struct queue_entry *q) int retval = -1; struct clip_entry* clipbuf; size_t clipsize; - enum offset_type type; index = id2index(id); if (index == -1) @@ -521,12 +499,10 @@ static int get_clip(long id, struct queue_entry *q) clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */ retval = cc[i].handle; } - type = TALK_HANDLE; - q->offset = retval; + q->handle = retval; q->length = clipsize; q->remaining = clipsize; - q->type = type; return 0; } @@ -596,33 +572,6 @@ alloc_err: index_handle = core_free(index_handle); return false; } - -static bool alloc_thumbnail_buf(void) -{ - int handle; - size_t size; - if (thumb_handle > 0) - return true; /* nothing to do? */ -#if CONFIG_CODEC == SWCODEC - /* try to allocate the max. first, and take whatever we can get if that - * fails */ - size = MAX_THUMBNAIL_BUFSIZE; - handle = core_alloc_ex("voice thumb", MAX_THUMBNAIL_BUFSIZE, &thumb_ops); - if (handle < 0) - { - size = core_allocatable(); - handle = core_alloc_ex("voice thumb", size, &thumb_ops); - } -#else - /* on HWCODEC, just use the rest of the remaining buffer, - * normal playback cannot happen anyway */ - size = audio_buffer_available(); - handle = core_alloc_ex("voice thumb", size, &thumb_ops); -#endif - thumb_handle = handle; - size_for_thumbnail = (handle > 0) ? size : 0; - return handle > 0; -} /* load the voice file into the mp3 buffer */ static bool load_voicefile_index(int fd) @@ -684,10 +633,6 @@ static bool load_voicefile_data(int fd) * VOICE_PAUSE clip is specially handled */ get_clip(VOICE_PAUSE, &silence); - /* not an error if this fails here, might try again when the - * actual thumbnails are attempted to be played back */ - alloc_thumbnail_buf(); - return true; } @@ -701,18 +646,13 @@ static void* commit_transfer(struct queue_entry *qe, size_t *size) { void *buf = NULL; /* shut up gcc */ static unsigned char *bufpos = commit_buffer; - int offset = qe->offset; #if CONFIG_CODEC != SWCODEC sent = MIN(qe->remaining, 0xFFFF); #else sent = qe->remaining; #endif sent = MIN((size_t)sent, sizeof(commit_buffer)); - switch (qe->type) - { - case TALK_HANDLE: buf = buflib_get_data(&clip_ctx, offset); break; - case THUMB_OFFSET: buf = core_get_data(thumb_handle) + offset; break; - } + buf = buflib_get_data(&clip_ctx, qe->handle); /* adjust buffer position to what has been played already */ buf += (qe->length - qe->remaining); memcpy(bufpos, buf, sent); @@ -724,8 +664,8 @@ static void* commit_transfer(struct queue_entry *qe, size_t *size) static inline bool is_silence(struct queue_entry *qe) { - if (silence.length > 0) /* silence clip available? */ - return (qe->offset == silence.offset && qe->type == silence.type); + if (silence.handle > 0) /* silence clip available? */ + return (qe->handle == silence.handle); else return false; } @@ -743,21 +683,15 @@ static void mp3_callback(const void** start, size_t* size) } talk_queue_lock(); - /* check if thumbnails have been played */ - if (qe->type == THUMB_OFFSET) - { - if (qe->remaining == 0 && (qe->length + qe->offset) == thumbnail_buf_used) - thumbnail_buf_used = 0; - } /* increment read position for the just played clip */ queue_read = (queue_read + 1) & QUEUE_MASK; if (QUEUE_LEVEL == 0) { - if (!is_silence(last_clip) && last_clip->type != THUMB_OFFSET) + if (!is_silence(last_clip)) { /* add silence clip when queue runs empty playing a voice clip, - * only if the previous clip wasn't silence or thumbnail */ + * only if the previous clip wasn't already silence */ queue[queue_write] = silence; queue_write = (queue_write + 1) & QUEUE_MASK; } @@ -793,7 +727,6 @@ void talk_force_shutup(void) unsigned char* search; unsigned char* end; int len; - unsigned offset; if (QUEUE_LEVEL == 0) /* has ended anyway */ return; @@ -802,13 +735,7 @@ void talk_force_shutup(void) #endif /* CONFIG_CPU == SH7034 */ /* search next frame boundary and continue up to there */ pos = search = mp3_get_pos(); - offset = queue[queue_read].offset; - switch (queue[queue_read].type) - { - case THUMB_OFFSET: end = core_get_data(thumb_handle) + offset; break; - case TALK_HANDLE: end = buflib_get_data(&clip_ctx, offset); break; - default: end = NULL; /* shut up gcc */ - } + end = buflib_get_data(&clip_ctx, queue[queue_read].handle); len = queue[queue_read].length; if (pos >= end && pos <= (end+len)) /* really our clip? */ @@ -914,7 +841,6 @@ static void queue_clip(struct queue_entry *clip, bool enqueue) return; } -#if CONFIG_CODEC == SWCODEC /* return if a voice codec is required or not */ static bool talk_voice_required(void) { @@ -922,7 +848,6 @@ static bool talk_voice_required(void) || (global_settings.talk_dir_clip) /* Thumbnail clips are required */ || (global_settings.talk_file_clip); } -#endif /***************** Public implementation *****************/ @@ -951,7 +876,7 @@ void talk_init(void) queue_write = queue_read = 0; /* reset the queue */ memset(&voicefile, 0, sizeof(voicefile)); - silence.offset = -1; /* pause clip not accessible */ + silence.handle = -1; /* pause clip not accessible */ voicefile_size = has_voicefile = 0; /* need to free these as their size depends on the voice file, and * this function is called when the talk voice file changes */ @@ -961,37 +886,46 @@ void talk_init(void) * and so we can re-use it if it's already allocated in any event */ filehandle = open_voicefile(); - if (filehandle < 0) - return; - - if (!load_voicefile_index(filehandle)) - goto out; - - /* Now determine the maximum buffer size needed for the voicefile. - * The below pretends the entire voicefile would be loaded. The buffer - * size is eventually capped later on in load_voicefile_data() */ - int num_clips = voicefile.id1_max + voicefile.id2_max; - int non_empty = num_clips; - int total_size = 0, avg_size; - struct clip_entry *clips = core_get_data(index_handle); - /* check for the average clip size to estimate the maximum number of - * clips the buffer can hold */ - for (int i = 0; i 0) { - if (clips[i].size) - total_size += ALIGN_UP(clips[i].size, sizeof(void *)); - else - non_empty -= 1; + if (!load_voicefile_index(filehandle)) + goto out; + + /* Now determine the maximum buffer size needed for the voicefile. + * The below pretends the entire voicefile would be loaded. The buffer + * size is eventually capped later on in load_voicefile_data() */ + int num_clips = voicefile.id1_max + voicefile.id2_max; + int non_empty = num_clips; + int total_size = 0, avg_size; + struct clip_entry *clips = core_get_data(index_handle); + /* check for the average clip size to estimate the maximum number of + * clips the buffer can hold */ + for (int i = 0; i size_for_thumbnail -thumb_used) - { /* Don't play truncated clips */ - close(fd); - return 0; - } + size = filesize(fd); #if CONFIG_CODEC != SWCODEC - lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ + size -= lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ #endif - size = read_to_handle(fd, thumb_handle, thumb_used, size_for_thumbnail - thumb_used); + /* free clips from cache until this one succeeds to allocate */ + while ((handle = buflib_alloc(&clip_ctx, size)) < 0) + oldest = free_oldest_clip(); + + size = read_to_handle_ex(fd, &clip_ctx, handle, 0, size); close(fd); /* ToDo: find audio, skip ID headers and trailers */ @@ -1150,21 +1080,25 @@ static int _talk_file(const char* filename, if (size > 0) /* Don't play missing clips */ { struct queue_entry clip; + clip.handle = handle; + clip.length = clip.remaining = size; #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) /* bitswap doesnt yield() */ - bitswap(core_get_data(thumb_handle), size); + bitswap(buflib_get_data(&clip_ctx, handle), size); #endif if(prefix_ids) /* prefix thumbnail by speaking these ids, but only now that we know there's actually a thumbnail to be spoken. */ talk_idarray(prefix_ids, true); - talk_queue_lock(); - thumbnail_buf_used = thumb_used + size; - talk_queue_unlock(); - clip = (struct queue_entry){ .offset = thumb_used, .length = size, .remaining = size, .type = THUMB_OFFSET }; + /* finally insert into metadata table. thumb clips go under the + * VOICEONLY_DELIMITER id so the cache can distinguish them from + * normal clips */ + add_cache_entry(handle, oldest, VOICEONLY_DELIMITER); queue_clip(&clip, true); } + else + buflib_free(&clip_ctx, handle); return size; } @@ -1566,6 +1500,7 @@ void talk_time(const struct tm *tm, bool enqueue) bool talk_get_debug_data(struct talk_debug_data *data) { char* p_lang = DEFAULT_VOICE_LANG; /* default */ + struct clip_cache_metadata *cc; memset(data, 0, sizeof(*data)); @@ -1593,17 +1528,21 @@ bool talk_get_debug_data(struct talk_debug_data *data) if (size > data->max_clipsize) data->max_clipsize = size; data->avg_clipsize += size; - if (clips[i].size & LOADED_MASK) - cached++; + } + cc = buflib_get_data(&clip_ctx, metadata_table_handle); + for (int i = 0; i < (int) max_clips; i++) + { + if (cc[i].handle > 0) + cached += 1; } data->avg_clipsize /= real_clips; data->num_empty_clips = data->num_clips - real_clips; data->memory_allocated = sizeof(commit_buffer) + sizeof(voicefile) + data->num_clips * sizeof(struct clip_entry) - + voicebuf_size + size_for_thumbnail; - data->memory_used = data->memory_allocated - size_for_thumbnail + thumbnail_buf_used; + + voicebuf_size; + data->memory_used = 0; if (talk_handle > 0) - data->memory_used -= buflib_available(&clip_ctx); + data->memory_used = data->memory_allocated - buflib_available(&clip_ctx); data->cached_clips = cached; data->cache_hits = cache_hits; data->cache_misses = cache_misses;