summaryrefslogtreecommitdiffstats
path: root/apps/talk.c
diff options
context:
space:
mode:
authorThomas Martitz <kugel@rockbox.org>2014-02-02 16:58:28 +0100
committerThomas Martitz <kugel@rockbox.org>2014-02-02 19:40:39 +0100
commitaf02a674c539ee76260be543c000637ffa6ce2af (patch)
tree5a8d1ed7879fcb503c6b3b119f4ad488c240f31a /apps/talk.c
parent03f373c9cd370e77766a511c574b3cf7bf8d9226 (diff)
downloadrockbox-af02a674c539ee76260be543c000637ffa6ce2af.tar.gz
rockbox-af02a674c539ee76260be543c000637ffa6ce2af.tar.bz2
rockbox-af02a674c539ee76260be543c000637ffa6ce2af.zip
talk.c: The new cache management is good enough to use it for .talk clips as well.
This unifies the talk.c for all possible voice payload. .talk clips are placed onto the same unified clip cache, along with normal clips. This allows for more effecient memory usage. The cache handling makes a slight difference between normal clips and .talk ones: .talk clips can be cached multiple and are always freed first.The extra logic to avoid loading multiple copies of .talks is not necessary because the will be freed first anyway. Change-Id: I88d056a0a613b129f5875f50fdb757b58bac0a42
Diffstat (limited to 'apps/talk.c')
-rw-r--r--apps/talk.c269
1 files changed, 104 insertions, 165 deletions
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<num_clips; i++)
+ if (filehandle > 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<num_clips; i++)
+ {
+ if (clips[i].size)
+ total_size += ALIGN_UP(clips[i].size, sizeof(void *));
+ else
+ non_empty -= 1;
+ }
+ avg_size = total_size / non_empty;
+ max_clips = MIN((int)(MAX_CLIP_BUFFER_SIZE/avg_size) + 1, non_empty);
+ /* account for possible thumb clips */
+ total_size += THUMBNAIL_RESERVE;
+ max_clips += 16;
+ voicefile_size = total_size;
+ has_voicefile = true;
}
- avg_size = total_size / non_empty;
- max_clips = MIN((int)(MAX_CLIP_BUFFER_SIZE/avg_size) + 1, non_empty);
- voicefile_size = total_size;
- /* additionally to the clips we need a table to record the age of the clips
+ else if (talk_voice_required())
+ {
+ /* create buffer just for thumb clips */
+ max_clips = 16;
+ voicefile_size = THUMBNAIL_RESERVE;
+ }
+ /* additionally to the clip we need a table to record the age of the clips
* so that, when memory is tight, only the most recently used ones are kept */
voicefile_size += sizeof(struct clip_cache_metadata) * max_clips;
/* compensate a bit for buflib alloc overhead. */
voicefile_size += BUFLIB_ALLOC_OVERHEAD * max_clips + 64;
- has_voicefile = true;
load_voicefile_data(filehandle);
@@ -1003,7 +937,6 @@ void talk_init(void)
out:
close(filehandle); /* close again, this was just to detect presence */
- filehandle = -1;
}
/* somebody else claims the mp3 buffer, e.g. for regular play/record */
@@ -1035,10 +968,10 @@ int talk_id(int32_t id, bool enqueue)
if (talk_handle <= 0 || index_handle <= 0) /* reload needed? */
{
int fd = open_voicefile();
- if (fd < 0
- || !load_voicefile_index(fd)
- || !load_voicefile_data(fd))
+ if (fd < 0 || !load_voicefile_index(fd))
return -1;
+ load_voicefile_data(fd);
+ close(fd);
}
if (id == -1) /* -1 is an indication for silence */
@@ -1100,7 +1033,7 @@ static int _talk_file(const char* filename,
{
int fd;
int size;
- int thumb_used;
+ int handle, oldest = -1;
#if CONFIG_CODEC != SWCODEC
struct mp3entry info;
#endif
@@ -1110,9 +1043,8 @@ static int _talk_file(const char* filename,
return -1; /* talking has been disabled */
if (!check_audio_status())
return -1;
-
- if (!alloc_thumbnail_buf())
- return -1;
+ if (talk_handle <= 0)
+ load_voicefile_data(-1);
#if CONFIG_CODEC != SWCODEC
if(mp3info(&info, filename)) /* use this to find real start */
@@ -1130,19 +1062,17 @@ static int _talk_file(const char* filename,
{
return 0;
}
-
- thumb_used = thumbnail_buf_used;
- if(filesize(fd) > 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;