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
This commit is contained in:
Thomas Martitz 2014-02-02 16:58:28 +01:00
parent 03f373c9cd
commit af02a674c5

View file

@ -80,14 +80,6 @@ const char* const file_thumbnail_ext = ".talk";
#define LOADED_MASK 0x80000000 /* MSB */ #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" #define DEFAULT_VOICE_LANG "english"
/***************** Data types *****************/ /***************** Data types *****************/
@ -124,13 +116,14 @@ struct voicefile_header /* file format of our voice file */
#endif #endif
#ifndef MAX_CLIP_BUFFER_SIZE #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 #endif
#define THUMBNAIL_RESERVE (50000)
/* Multiple thumbnails can be loaded back-to-back in this buffer. */ /* Multiple thumbnails can be loaded back-to-back in this buffer. */
static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in
thumbnail buffer */ thumbnail buffer */
static long size_for_thumbnail; /* total thumbnail buffer size */
static struct voicefile_header voicefile; /* loaded voicefile */ static struct voicefile_header voicefile; /* loaded voicefile */
static bool has_voicefile; /* a voicefile file is present */ static bool has_voicefile; /* a voicefile file is present */
static bool need_shutup; /* is there possibly any voice playing to be shutup */ 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 */ struct queue_entry /* one entry of the internal queue */
{ {
int offset; /* actually a buflib handle if type == HANDLE */ int handle; /* buflib handle to the clip data */
int length; /* total length of the clip */ int length; /* total length of the clip */
int remaining; /* bytes that still need to be deoded */ 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; static struct buflib_context clip_ctx;
@ -186,7 +172,7 @@ static struct queue_entry silence, *last_clip;
/***************** Private implementation *****************/ /***************** 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) 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; 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 = { static struct buflib_callbacks talk_ops = {
.move_callback = move_callback, .move_callback = move_callback,
.sync_callback = sync_callback, .sync_callback = sync_callback,
.shrink_callback = shrink_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) 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); struct clip_cache_metadata *cc = buflib_get_data(&clip_ctx, metadata_table_handle);
for(age = i = 0, now = current_tick; i < max_clips; i++) for(age = i = 0, now = current_tick; i < max_clips; i++)
{ {
if (cc[i].handle && (now - cc[i].tick) > age if (cc[i].handle)
&& cc[i].voice_id != VOICE_PAUSE) /* never consider silence */
{ {
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; age = now - cc[i].tick;
oldest = i; 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 = &cc[oldest];
cc->handle = buflib_free(&clip_ctx, cc->handle); cc->handle = buflib_free(&clip_ctx, cc->handle);
/* need to clear the LOADED bit too */ /* need to clear the LOADED bit too (not for thumb clips) */
if (cc->voice_id != VOICEONLY_DELIMITER)
{
clipbuf = core_get_data(index_handle); clipbuf = core_get_data(index_handle);
clipbuf[id2index(cc->voice_id)].size &= ~LOADED_MASK; clipbuf[id2index(cc->voice_id)].size &= ~LOADED_MASK;
}
return oldest; return oldest;
} }
@ -458,6 +434,8 @@ static void load_initial_clips(int fd)
int handle; int handle;
struct clip_entry* clipbuf = core_get_data(index_handle); struct clip_entry* clipbuf = core_get_data(index_handle);
size_t clipsize = clipbuf[index].size; size_t clipsize = clipbuf[index].size;
ssize_t ret;
if (clipsize == 0) /* clip not included in voicefile */ if (clipsize == 0) /* clip not included in voicefile */
continue; continue;
@ -465,7 +443,8 @@ static void load_initial_clips(int fd)
if (handle < 0) if (handle < 0)
break; break;
if (read_clip_data(fd, index, handle) < 0) ret = read_clip_data(fd, index, handle);
if (ret < 0)
break; break;
add_cache_entry(handle, i++, index2id(index)); add_cache_entry(handle, i++, index2id(index));
@ -480,7 +459,6 @@ static int get_clip(long id, struct queue_entry *q)
int retval = -1; int retval = -1;
struct clip_entry* clipbuf; struct clip_entry* clipbuf;
size_t clipsize; size_t clipsize;
enum offset_type type;
index = id2index(id); index = id2index(id);
if (index == -1) 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 */ clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */
retval = cc[i].handle; retval = cc[i].handle;
} }
type = TALK_HANDLE;
q->offset = retval; q->handle = retval;
q->length = clipsize; q->length = clipsize;
q->remaining = clipsize; q->remaining = clipsize;
q->type = type;
return 0; return 0;
} }
@ -597,33 +573,6 @@ alloc_err:
return false; 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 */ /* load the voice file into the mp3 buffer */
static bool load_voicefile_index(int fd) static bool load_voicefile_index(int fd)
{ {
@ -684,10 +633,6 @@ static bool load_voicefile_data(int fd)
* VOICE_PAUSE clip is specially handled */ * VOICE_PAUSE clip is specially handled */
get_clip(VOICE_PAUSE, &silence); 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; return true;
} }
@ -701,18 +646,13 @@ static void* commit_transfer(struct queue_entry *qe, size_t *size)
{ {
void *buf = NULL; /* shut up gcc */ void *buf = NULL; /* shut up gcc */
static unsigned char *bufpos = commit_buffer; static unsigned char *bufpos = commit_buffer;
int offset = qe->offset;
#if CONFIG_CODEC != SWCODEC #if CONFIG_CODEC != SWCODEC
sent = MIN(qe->remaining, 0xFFFF); sent = MIN(qe->remaining, 0xFFFF);
#else #else
sent = qe->remaining; sent = qe->remaining;
#endif #endif
sent = MIN((size_t)sent, sizeof(commit_buffer)); sent = MIN((size_t)sent, sizeof(commit_buffer));
switch (qe->type) buf = buflib_get_data(&clip_ctx, qe->handle);
{
case TALK_HANDLE: buf = buflib_get_data(&clip_ctx, offset); break;
case THUMB_OFFSET: buf = core_get_data(thumb_handle) + offset; break;
}
/* adjust buffer position to what has been played already */ /* adjust buffer position to what has been played already */
buf += (qe->length - qe->remaining); buf += (qe->length - qe->remaining);
memcpy(bufpos, buf, sent); 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) static inline bool is_silence(struct queue_entry *qe)
{ {
if (silence.length > 0) /* silence clip available? */ if (silence.handle > 0) /* silence clip available? */
return (qe->offset == silence.offset && qe->type == silence.type); return (qe->handle == silence.handle);
else else
return false; return false;
} }
@ -743,21 +683,15 @@ static void mp3_callback(const void** start, size_t* size)
} }
talk_queue_lock(); 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 */ /* increment read position for the just played clip */
queue_read = (queue_read + 1) & QUEUE_MASK; queue_read = (queue_read + 1) & QUEUE_MASK;
if (QUEUE_LEVEL == 0) 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, { /* 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[queue_write] = silence;
queue_write = (queue_write + 1) & QUEUE_MASK; queue_write = (queue_write + 1) & QUEUE_MASK;
} }
@ -793,7 +727,6 @@ void talk_force_shutup(void)
unsigned char* search; unsigned char* search;
unsigned char* end; unsigned char* end;
int len; int len;
unsigned offset;
if (QUEUE_LEVEL == 0) /* has ended anyway */ if (QUEUE_LEVEL == 0) /* has ended anyway */
return; return;
@ -802,13 +735,7 @@ void talk_force_shutup(void)
#endif /* CONFIG_CPU == SH7034 */ #endif /* CONFIG_CPU == SH7034 */
/* search next frame boundary and continue up to there */ /* search next frame boundary and continue up to there */
pos = search = mp3_get_pos(); pos = search = mp3_get_pos();
offset = queue[queue_read].offset; end = buflib_get_data(&clip_ctx, queue[queue_read].handle);
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 */
}
len = queue[queue_read].length; len = queue[queue_read].length;
if (pos >= end && pos <= (end+len)) /* really our clip? */ if (pos >= end && pos <= (end+len)) /* really our clip? */
@ -914,7 +841,6 @@ static void queue_clip(struct queue_entry *clip, bool enqueue)
return; return;
} }
#if CONFIG_CODEC == SWCODEC
/* return if a voice codec is required or not */ /* return if a voice codec is required or not */
static bool talk_voice_required(void) 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_dir_clip) /* Thumbnail clips are required */
|| (global_settings.talk_file_clip); || (global_settings.talk_file_clip);
} }
#endif
/***************** Public implementation *****************/ /***************** Public implementation *****************/
@ -951,7 +876,7 @@ void talk_init(void)
queue_write = queue_read = 0; /* reset the queue */ queue_write = queue_read = 0; /* reset the queue */
memset(&voicefile, 0, sizeof(voicefile)); memset(&voicefile, 0, sizeof(voicefile));
silence.offset = -1; /* pause clip not accessible */ silence.handle = -1; /* pause clip not accessible */
voicefile_size = has_voicefile = 0; voicefile_size = has_voicefile = 0;
/* need to free these as their size depends on the voice file, and /* need to free these as their size depends on the voice file, and
* this function is called when the talk voice file changes */ * this function is called when the talk voice file changes */
@ -961,9 +886,8 @@ void talk_init(void)
* and so we can re-use it if it's already allocated in any event */ * and so we can re-use it if it's already allocated in any event */
filehandle = open_voicefile(); filehandle = open_voicefile();
if (filehandle < 0) if (filehandle > 0)
return; {
if (!load_voicefile_index(filehandle)) if (!load_voicefile_index(filehandle))
goto out; goto out;
@ -985,13 +909,23 @@ void talk_init(void)
} }
avg_size = total_size / non_empty; avg_size = total_size / non_empty;
max_clips = MIN((int)(MAX_CLIP_BUFFER_SIZE/avg_size) + 1, 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; voicefile_size = total_size;
/* additionally to the clips we need a table to record the age of the clips has_voicefile = true;
}
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 */ * so that, when memory is tight, only the most recently used ones are kept */
voicefile_size += sizeof(struct clip_cache_metadata) * max_clips; voicefile_size += sizeof(struct clip_cache_metadata) * max_clips;
/* compensate a bit for buflib alloc overhead. */ /* compensate a bit for buflib alloc overhead. */
voicefile_size += BUFLIB_ALLOC_OVERHEAD * max_clips + 64; voicefile_size += BUFLIB_ALLOC_OVERHEAD * max_clips + 64;
has_voicefile = true;
load_voicefile_data(filehandle); load_voicefile_data(filehandle);
@ -1003,7 +937,6 @@ void talk_init(void)
out: out:
close(filehandle); /* close again, this was just to detect presence */ close(filehandle); /* close again, this was just to detect presence */
filehandle = -1;
} }
/* somebody else claims the mp3 buffer, e.g. for regular play/record */ /* 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? */ if (talk_handle <= 0 || index_handle <= 0) /* reload needed? */
{ {
int fd = open_voicefile(); int fd = open_voicefile();
if (fd < 0 if (fd < 0 || !load_voicefile_index(fd))
|| !load_voicefile_index(fd)
|| !load_voicefile_data(fd))
return -1; return -1;
load_voicefile_data(fd);
close(fd);
} }
if (id == -1) /* -1 is an indication for silence */ if (id == -1) /* -1 is an indication for silence */
@ -1100,7 +1033,7 @@ static int _talk_file(const char* filename,
{ {
int fd; int fd;
int size; int size;
int thumb_used; int handle, oldest = -1;
#if CONFIG_CODEC != SWCODEC #if CONFIG_CODEC != SWCODEC
struct mp3entry info; struct mp3entry info;
#endif #endif
@ -1110,9 +1043,8 @@ static int _talk_file(const char* filename,
return -1; /* talking has been disabled */ return -1; /* talking has been disabled */
if (!check_audio_status()) if (!check_audio_status())
return -1; return -1;
if (talk_handle <= 0)
if (!alloc_thumbnail_buf()) load_voicefile_data(-1);
return -1;
#if CONFIG_CODEC != SWCODEC #if CONFIG_CODEC != SWCODEC
if(mp3info(&info, filename)) /* use this to find real start */ if(mp3info(&info, filename)) /* use this to find real start */
@ -1130,19 +1062,17 @@ static int _talk_file(const char* filename,
{ {
return 0; return 0;
} }
size = filesize(fd);
thumb_used = thumbnail_buf_used;
if(filesize(fd) > size_for_thumbnail -thumb_used)
{ /* Don't play truncated clips */
close(fd);
return 0;
}
#if CONFIG_CODEC != SWCODEC #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 #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); close(fd);
/* ToDo: find audio, skip ID headers and trailers */ /* 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 */ if (size > 0) /* Don't play missing clips */
{ {
struct queue_entry clip; struct queue_entry clip;
clip.handle = handle;
clip.length = clip.remaining = size;
#if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR)
/* bitswap doesnt yield() */ /* bitswap doesnt yield() */
bitswap(core_get_data(thumb_handle), size); bitswap(buflib_get_data(&clip_ctx, handle), size);
#endif #endif
if(prefix_ids) if(prefix_ids)
/* prefix thumbnail by speaking these ids, but only now /* prefix thumbnail by speaking these ids, but only now
that we know there's actually a thumbnail to be that we know there's actually a thumbnail to be
spoken. */ spoken. */
talk_idarray(prefix_ids, true); talk_idarray(prefix_ids, true);
talk_queue_lock(); /* finally insert into metadata table. thumb clips go under the
thumbnail_buf_used = thumb_used + size; * VOICEONLY_DELIMITER id so the cache can distinguish them from
talk_queue_unlock(); * normal clips */
clip = (struct queue_entry){ .offset = thumb_used, .length = size, .remaining = size, .type = THUMB_OFFSET }; add_cache_entry(handle, oldest, VOICEONLY_DELIMITER);
queue_clip(&clip, true); queue_clip(&clip, true);
} }
else
buflib_free(&clip_ctx, handle);
return size; 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) bool talk_get_debug_data(struct talk_debug_data *data)
{ {
char* p_lang = DEFAULT_VOICE_LANG; /* default */ char* p_lang = DEFAULT_VOICE_LANG; /* default */
struct clip_cache_metadata *cc;
memset(data, 0, sizeof(*data)); memset(data, 0, sizeof(*data));
@ -1593,17 +1528,21 @@ bool talk_get_debug_data(struct talk_debug_data *data)
if (size > data->max_clipsize) if (size > data->max_clipsize)
data->max_clipsize = size; data->max_clipsize = size;
data->avg_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->avg_clipsize /= real_clips;
data->num_empty_clips = data->num_clips - real_clips; data->num_empty_clips = data->num_clips - real_clips;
data->memory_allocated = sizeof(commit_buffer) + sizeof(voicefile) data->memory_allocated = sizeof(commit_buffer) + sizeof(voicefile)
+ data->num_clips * sizeof(struct clip_entry) + data->num_clips * sizeof(struct clip_entry)
+ voicebuf_size + size_for_thumbnail; + voicebuf_size;
data->memory_used = data->memory_allocated - size_for_thumbnail + thumbnail_buf_used; data->memory_used = 0;
if (talk_handle > 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->cached_clips = cached;
data->cache_hits = cache_hits; data->cache_hits = cache_hits;
data->cache_misses = cache_misses; data->cache_misses = cache_misses;