diff --git a/apps/gui/skin_engine/skin_render.c b/apps/gui/skin_engine/skin_render.c index 28483cbc49..0d36711e91 100644 --- a/apps/gui/skin_engine/skin_render.c +++ b/apps/gui/skin_engine/skin_render.c @@ -280,13 +280,11 @@ static bool do_non_text_tags(struct gui_wps *gwps, struct skin_draw_info *info, if (do_refresh && aa) { int handle = playback_current_aa_hid(data->playback_aa_slot); -#if 0 /* FIXME: FS#12797*/ if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF)) { struct dim dim = {aa->width, aa->height}; handle = radio_get_art_hid(&dim); } -#endif aa->draw_handle = handle; } break; diff --git a/apps/mpeg.c b/apps/mpeg.c index 2783a24085..5fd1bfdb82 100644 --- a/apps/mpeg.c +++ b/apps/mpeg.c @@ -527,7 +527,13 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s ssize_t size = (ssize_t)old_size - wanted_size; /* keep at least 256K for the buffering */ if ((size - extradata_size) < AUDIO_BUFFER_RESERVE) - return BUFLIB_CB_CANNOT_SHRINK; + { + /* check if buflib needs the memory really hard. if yes we give + * up playback for now, otherwise refuse to shrink to keep at least + * 256K for the buffering */ + if ((hints & BUFLIB_SHRINK_POS_MASK) != BUFLIB_SHRINK_POS_MASK) + return BUFLIB_CB_CANNOT_SHRINK; + } /* TODO: Do it without stopping playback, if possible */ bool playing = (audio_status() & AUDIO_STATUS_PLAY) == AUDIO_STATUS_PLAY; long offset = audio_current_track()->offset; @@ -539,7 +545,6 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s } else audio_stop(); - talk_buffer_steal(); /* we obtain control over the buffer */ switch (hints & BUFLIB_SHRINK_POS_MASK) { @@ -551,6 +556,12 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s core_shrink(handle, start + wanted_size, size); audio_reset_buffer_noalloc(start + wanted_size, size); break; + case BUFLIB_SHRINK_POS_MASK: + audiobuf_handle = core_free(audiobuf_handle); + mpeg_audiobuf = NULL; + talk_buffer_set_policy(TALK_BUFFER_DEFAULT); + playing = false; + break; } if (playing) { /* safe to call even from the audio thread (due to queue_post()) */ @@ -565,45 +576,6 @@ static struct buflib_callbacks ops = { .shrink_callback = shrink_callback, }; -static size_t audio_talkbuf_init(char *bufstart) -{ - size_t ret = talkbuf_init(bufstart); - if (ret > (size_t)audiobuflen) /* does the voice even fit? */ - { - talk_buffer_steal(); - return 0; - } - return ret; -} - -unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size) -{ - (void)talk_buf; /* always grab the voice buffer for now */ - - if (audio_is_initialized) - audio_hard_stop(); - - if (!buffer_size) /* special case for talk_init() */ - return NULL; - - if (!audiobuf_handle) - { - size_t bufsize; - /* audio_hard_stop() frees audiobuf, so re-aquire */ - audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops); - audiobuflen = bufsize; - if (buffer_size) - *buffer_size = audiobuflen; - } - mpeg_audiobuf = core_get_data(audiobuf_handle); - /* tell talk about the new buffer, don't re-enable just yet because the - * buffer is stolen */ - audio_talkbuf_init(mpeg_audiobuf); - - return mpeg_audiobuf; -} - - #ifndef SIMULATOR /* Send callback events to notify about removing old tracks. */ static void generate_unbuffer_events(void) @@ -2755,7 +2727,6 @@ size_t audio_buffer_available(void) static void audio_reset_buffer_noalloc(void* buf, size_t bufsize) { - talk_buffer_steal(); /* will use the mp3 buffer */ mpeg_audiobuf = buf; audiobuflen = bufsize; if (global_settings.cuesheet) @@ -2764,16 +2735,20 @@ static void audio_reset_buffer_noalloc(void* buf, size_t bufsize) mpeg_audiobuf = SKIPBYTES(mpeg_audiobuf, sizeof(struct cuesheet)); audiobuflen -= sizeof(struct cuesheet); } - audio_talkbuf_init(mpeg_audiobuf); } static void audio_reset_buffer(void) { size_t bufsize = audiobuflen; - /* alloc buffer if it's was never allocated or freed by audio_hard_stop() */ + /* alloc buffer if it's was never allocated or freed by audio_hard_stop() + * because voice cannot be played during audio playback make + * talk.c give up all buffers and disable itself */ if (!audiobuf_handle) + { + talk_buffer_set_policy(TALK_BUFFER_LOOSE); audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops); + } audio_reset_buffer_noalloc(core_get_data(audiobuf_handle), bufsize); } @@ -2818,6 +2793,8 @@ void audio_play(long offset) void audio_stop(void) { + if (audiobuf_handle <= 0) + return; /* nothing to do, must be hard-stopped already */ #ifndef SIMULATOR mpeg_stop_done = false; queue_post(&mpeg_queue, MPEG_STOP, 0); @@ -2828,8 +2805,6 @@ void audio_stop(void) is_playing = false; playing = false; #endif /* SIMULATOR */ - /* give voice our entire buffer */ - audio_talkbuf_init(mpeg_audiobuf); } /* dummy */ @@ -2840,13 +2815,12 @@ void audio_stop_recording(void) void audio_hard_stop(void) { - audio_stop(); - /* tell voice we obtain the buffer before freeing */ - talk_buffer_steal(); if (audiobuf_handle > 0) { + audio_stop(); audiobuf_handle = core_free(audiobuf_handle); mpeg_audiobuf = NULL; + talk_buffer_set_policy(TALK_BUFFER_DEFAULT); } } diff --git a/apps/playback.c b/apps/playback.c index b240e95acd..5e234beb36 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -110,7 +110,6 @@ static enum audio_buffer_state { AUDIOBUF_STATE_TRASHED = -1, /* trashed; must be reset */ AUDIOBUF_STATE_INITIALIZED = 0, /* voice+audio OR audio-only */ - AUDIOBUF_STATE_VOICED_ONLY = 1, /* voice-only */ } buffer_state = AUDIOBUF_STATE_TRASHED; /* (A,O) */ /** Main state control **/ @@ -729,60 +728,42 @@ size_t audio_buffer_available(void) /* Set up the audio buffer for playback * filebuflen must be pre-initialized with the maximum size */ static void audio_reset_buffer_noalloc( - void *filebuf, enum audio_buffer_state state) + void *filebuf) { /* * Layout audio buffer as follows: - * [[|TALK]|SCRATCH|BUFFERING|PCM] + * [|SCRATCH|BUFFERING|PCM] */ - - /* see audio_get_recording_buffer if this is modified */ logf("%s()", __func__); /* If the setup of anything allocated before the file buffer is changed, do check the adjustments after the buffer_alloc call as it will likely be affected and need sliding over */ - - /* Initially set up file buffer as all space available */ size_t allocsize; + /* Subtract whatever the pcm buffer says it used plus the guard + buffer */ + allocsize = pcmbuf_init(filebuf + filebuflen); - /* Subtract whatever voice needs (we're called when promoting - the state only) */ - allocsize = talkbuf_init(filebuf); + /* Make sure filebuflen is a pointer sized multiple after + adjustment */ allocsize = ALIGN_UP(allocsize, sizeof (intptr_t)); if (allocsize > filebuflen) goto bufpanic; + filebuflen -= allocsize; + + /* Scratch memory */ + allocsize = scratch_mem_size(); + if (allocsize > filebuflen) + goto bufpanic; + + scratch_mem_init(filebuf); filebuf += allocsize; filebuflen -= allocsize; - if (state == AUDIOBUF_STATE_INITIALIZED) - { - /* Subtract whatever the pcm buffer says it used plus the guard - buffer */ - allocsize = pcmbuf_init(filebuf + filebuflen); + buffering_reset(filebuf, filebuflen); - /* Make sure filebuflen is a pointer sized multiple after - adjustment */ - allocsize = ALIGN_UP(allocsize, sizeof (intptr_t)); - if (allocsize > filebuflen) - goto bufpanic; - - filebuflen -= allocsize; - - /* Scratch memory */ - allocsize = scratch_mem_size(); - if (allocsize > filebuflen) - goto bufpanic; - - scratch_mem_init(filebuf); - filebuf += allocsize; - filebuflen -= allocsize; - - buffering_reset(filebuf, filebuflen); - } - - buffer_state = state; + buffer_state = AUDIOBUF_STATE_INITIALIZED; #if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) /* Make sure everything adds up - yes, some info is a bit redundant but @@ -811,15 +792,24 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s /* codec messages */ { Q_AUDIO_PLAY, Q_AUDIO_PLAY }, }; + bool give_up = false; /* filebuflen is, at this point, the buffering.c buffer size, * i.e. the audiobuf except voice, scratch mem, pcm, ... */ ssize_t extradata_size = old_size - filebuflen; /* check what buflib requests */ size_t wanted_size = (hints & BUFLIB_SHRINK_SIZE_MASK); ssize_t size = (ssize_t)old_size - wanted_size; - /* keep at least 256K for the buffering */ + if ((size - extradata_size) < AUDIO_BUFFER_RESERVE) - return BUFLIB_CB_CANNOT_SHRINK; + { + /* check if buflib needs the memory really hard. if yes we give + * up playback for now, otherwise refuse to shrink to keep at least + * 256K for the buffering */ + if ((hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) + give_up = true; + else + return BUFLIB_CB_CANNOT_SHRINK; + } /* TODO: Do it without stopping playback, if possible */ @@ -852,20 +842,26 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s #ifdef PLAYBACK_VOICE voice_stop(); #endif - /* we should be free to change the buffer now - * set final buffer size before calling audio_reset_buffer_noalloc() + + /* we should be free to change the buffer now */ + if (give_up) + { + buffer_state = AUDIOBUF_STATE_TRASHED; + audiobuf_handle = core_free(audiobuf_handle); + return BUFLIB_CB_OK; + } + /* set final buffer size before calling audio_reset_buffer_noalloc() * (now it's the total size, the call will subtract voice etc) */ filebuflen = size; switch (hints & BUFLIB_SHRINK_POS_MASK) { case BUFLIB_SHRINK_POS_BACK: core_shrink(handle, start, size); - audio_reset_buffer_noalloc(start, buffer_state); + audio_reset_buffer_noalloc(start); break; case BUFLIB_SHRINK_POS_FRONT: core_shrink(handle, start + wanted_size, size); - audio_reset_buffer_noalloc(start + wanted_size, - buffer_state); + audio_reset_buffer_noalloc(start + wanted_size); break; } if (playing || play_queued) @@ -882,7 +878,7 @@ static struct buflib_callbacks ops = { .shrink_callback = shrink_callback, }; -static void audio_reset_buffer(enum audio_buffer_state state) +static void audio_reset_buffer(void) { if (audiobuf_handle > 0) { @@ -890,9 +886,13 @@ static void audio_reset_buffer(enum audio_buffer_state state) audiobuf_handle = 0; } audiobuf_handle = core_alloc_maximum("audiobuf", &filebuflen, &ops); - unsigned char *filebuf = core_get_data(audiobuf_handle); - audio_reset_buffer_noalloc(filebuf, state); + if (audiobuf_handle > 0) + audio_reset_buffer_noalloc(core_get_data(audiobuf_handle)); + else + /* someone is abusing core_alloc_maximum(). Fix this evil guy instead of + * trying to handle OOM without hope */ + panicf("%s(): OOM!\n", __func__); } /* Set the buffer margin to begin rebuffering when 'seconds' from empty */ @@ -2033,7 +2033,7 @@ static int audio_fill_file_buffer(void) if (buffer_state != AUDIOBUF_STATE_INITIALIZED || !pcmbuf_is_same_size()) { - audio_reset_buffer(AUDIOBUF_STATE_INITIALIZED); + audio_reset_buffer(); } logf("Starting buffer fill"); @@ -2464,7 +2464,7 @@ static void audio_start_playback(size_t offset, unsigned int flags) /* Mark the buffer dirty - if not playing, it will be reset next time */ if (buffer_state == AUDIOBUF_STATE_INITIALIZED) - buffer_state = AUDIOBUF_STATE_VOICED_ONLY; + buffer_state = AUDIOBUF_STATE_TRASHED; } if (old_status != PLAY_STOPPED) @@ -3511,88 +3511,6 @@ void audio_flush_and_reload_tracks(void) audio_queue_post(Q_AUDIO_FLUSH, 0); } -/* Return the pointer to the main audio buffer, optionally preserving - voicing */ -unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size) -{ - unsigned char *buf; - - if (audio_is_initialized && thread_self() != audio_thread_id) - { - audio_hard_stop(); - } - /* else buffer_state will be AUDIOBUF_STATE_TRASHED at this point */ - - if (buffer_size == NULL) - { - /* Special case for talk_init to use since it already knows it's - trashed */ - buffer_state = AUDIOBUF_STATE_TRASHED; - return NULL; - } - - /* make sure buffer is freed and re-allocated to simplify code below - * (audio_hard_stop() likely has done that already) */ - if (audiobuf_handle > 0) - audiobuf_handle = core_free(audiobuf_handle); - - audiobuf_handle = core_alloc_maximum("audiobuf", &filebuflen, &ops); - buf = core_get_data(audiobuf_handle); - - if (buffer_state == AUDIOBUF_STATE_INITIALIZED) - buffering_reset(NULL, 0); /* mark buffer invalid */ - - if (talk_buf || !talk_voice_required()) - { - logf("get buffer: talk, audio"); - /* Ok to use everything from audiobuf - voice is loaded, - the talk buffer is not needed because voice isn't being used, or - could be AUDIOBUF_STATE_TRASHED already. If state is - AUDIOBUF_STATE_VOICED_ONLY, no problem as long as memory isn't - written without the caller knowing what's going on. Changing certain - settings may move it to a worse condition but the memory in use by - something else will remain undisturbed. - */ - if (buffer_state != AUDIOBUF_STATE_TRASHED) - { - talk_buffer_steal(); - buffer_state = AUDIOBUF_STATE_TRASHED; - } - } - else - { - logf("get buffer: audio"); - /* Safe to just return this if already AUDIOBUF_STATE_VOICED_ONLY or - still AUDIOBUF_STATE_INITIALIZED */ - size_t talkbuf_size = talkbuf_init(buf); - buf += talkbuf_size; /* Skip talk buffer */ - filebuflen -= talkbuf_size; - buffer_state = AUDIOBUF_STATE_VOICED_ONLY; - } - - *buffer_size = filebuflen; - return buf; -} - -/* Restore audio buffer to a particular state (promoting status) */ -bool audio_restore_playback(int type) -{ - switch (type) - { - case AUDIO_WANT_PLAYBACK: - if (buffer_state != AUDIOBUF_STATE_INITIALIZED) - audio_reset_buffer(AUDIOBUF_STATE_INITIALIZED); - return true; - case AUDIO_WANT_VOICE: - if (buffer_state == AUDIOBUF_STATE_TRASHED) - audio_reset_buffer(AUDIOBUF_STATE_VOICED_ONLY); - return true; - default: - return false; - } -} - - /** --- Miscellaneous public interfaces --- **/ #ifdef HAVE_ALBUMART diff --git a/apps/playback.h b/apps/playback.h index f56bbfdff0..177768ded3 100644 --- a/apps/playback.h +++ b/apps/playback.h @@ -80,12 +80,6 @@ void audio_set_cuesheet(bool enable); void audio_set_crossfade(int enable); #endif -enum -{ - AUDIO_WANT_PLAYBACK = 0, - AUDIO_WANT_VOICE, -}; -bool audio_restore_playback(int type); /* Restores the audio buffer to handle the requested playback */ size_t audio_get_filebuflen(void); unsigned int playback_status(void); diff --git a/apps/playlist.c b/apps/playlist.c index 35b7e35baa..3d930cf3f9 100755 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -104,6 +104,7 @@ #include "rbunicode.h" #include "root_menu.h" #include "plugin.h" /* To borrow a temp buffer to rewrite a .m3u8 file */ +#include "panic.h" #define PLAYLIST_CONTROL_FILE_VERSION 2 @@ -532,13 +533,6 @@ static int add_indices_to_playlist(struct playlist_info* playlist, splash(0, ID2P(LANG_WAIT)); - if (!buffer) - { - /* use mp3 buffer for maximum load speed */ - audio_stop(); - buffer = audio_get_buffer(false, &buflen); - } - store_index = true; while(1) @@ -2077,8 +2071,26 @@ int playlist_create(const char *dir, const char *file) new_playlist(playlist, dir, file); if (file) - /* load the playlist file */ - add_indices_to_playlist(playlist, NULL, 0); + { + /* dummy ops with no callbacks, needed because by + * default buflib buffers can be moved around which must be avoided */ + static struct buflib_callbacks dummy_ops; + int handle; + size_t buflen; + /* use mp3 buffer for maximum load speed */ + handle = core_alloc_maximum("temp", &buflen, &dummy_ops); + if (handle > 0) + { + /* load the playlist file */ + add_indices_to_playlist(playlist, core_get_data(handle), buflen); + core_free(handle); + } + else + { + /* should not happen */ + panicf("%s(): OOM", __func__); + } + } return 0; } @@ -2094,14 +2106,22 @@ int playlist_resume(void) struct playlist_info* playlist = ¤t_playlist; char *buffer; size_t buflen; + int handle; int nread; int total_read = 0; int control_file_size = 0; bool first = true; bool sorted = true; + int result = -1; + /* dummy ops with no callbacks, needed because by + * default buflib buffers can be moved around which must be avoided */ + static struct buflib_callbacks dummy_ops; /* use mp3 buffer for maximum load speed */ - buffer = (char *)audio_get_buffer(false, &buflen); + handle = core_alloc_maximum("temp", &buflen, &dummy_ops); + if (handle < 0) + panicf("%s(): OOM", __func__); + buffer = core_get_data(handle); empty_playlist(playlist, true); @@ -2110,7 +2130,7 @@ int playlist_resume(void) if (playlist->control_fd < 0) { splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - return -1; + goto out; } playlist->control_created = true; @@ -2118,7 +2138,7 @@ int playlist_resume(void) if (control_file_size <= 0) { splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - return -1; + goto out; } /* read a small amount first to get the header */ @@ -2127,14 +2147,14 @@ int playlist_resume(void) if(nread <= 0) { splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); - return -1; + goto out; } playlist->started = true; while (1) { - int result = 0; + result = 0; int count; enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; int last_newline = 0; @@ -2195,7 +2215,10 @@ int playlist_resume(void) version = atoi(str1); if (version != PLAYLIST_CONTROL_FILE_VERSION) - return -1; + { + result = -1; + goto out; + } update_playlist_filename(playlist, str2, str3); @@ -2204,7 +2227,7 @@ int playlist_resume(void) /* NOTE: add_indices_to_playlist() overwrites the audiobuf so we need to reload control file data */ - add_indices_to_playlist(playlist, NULL, 0); + add_indices_to_playlist(playlist, buffer, buflen); } else if (str2[0] != '\0') { @@ -2242,7 +2265,10 @@ int playlist_resume(void) buffer */ if (add_track_to_playlist(playlist, str3, position, queue, total_read+(str3-buffer)) < 0) - return -1; + { + result = -1; + goto out; + } playlist->last_insert_pos = last_position; @@ -2264,7 +2290,10 @@ int playlist_resume(void) if (remove_track_from_playlist(playlist, position, false) < 0) - return -1; + { + result = -1; + goto out; + } break; } @@ -2291,7 +2320,10 @@ int playlist_resume(void) if (randomise_playlist(playlist, seed, false, false) < 0) - return -1; + { + result = -1; + goto out; + } sorted = false; break; } @@ -2308,7 +2340,10 @@ int playlist_resume(void) playlist->first_index = atoi(str1); if (sort_playlist(playlist, false, false) < 0) - return -1; + { + result = -1; + goto out; + } sorted = true; break; @@ -2421,13 +2456,14 @@ int playlist_resume(void) if (result < 0) { splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID)); - return result; + goto out; } if (useraborted) { splash(HZ*2, ID2P(LANG_CANCEL)); - return -1; + result = -1; + goto out; } if (!newline || (exit_loop && count 0) + plugin_buffer_handle = core_free(plugin_buffer_handle); } splash(0, ID2P(LANG_WAIT)); @@ -878,6 +882,9 @@ int plugin_load(const char* plugin, const void* parameter) touchscreen_set_mode(TOUCHSCREEN_BUTTON); #endif + /* allow voice to back off if the plugin needs lots of memory */ + talk_buffer_set_policy(TALK_BUFFER_LOOSE); + #ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE open_files = 0; #endif @@ -891,8 +898,12 @@ int plugin_load(const char* plugin, const void* parameter) { /* close handle if plugin is no tsr one */ lc_close(current_plugin_handle); current_plugin_handle = NULL; + if (plugin_buffer_handle > 0) + plugin_buffer_handle = core_free(plugin_buffer_handle); } + talk_buffer_set_policy(TALK_BUFFER_DEFAULT); + /* Go back to the global setting in case the plugin changed it */ #ifdef HAVE_TOUCHSCREEN touchscreen_set_mode(global_settings.touch_mode); @@ -984,8 +995,12 @@ void* plugin_get_buffer(size_t *buffer_size) */ void* plugin_get_audio_buffer(size_t *buffer_size) { - audio_stop(); - return audio_get_buffer(true, buffer_size); + /* dummy ops with no callbacks, needed because by + * default buflib buffers can be moved around which must be avoided */ + static struct buflib_callbacks dummy_ops; + plugin_buffer_handle = core_alloc_maximum("plugin audio buf", buffer_size, + &dummy_ops); + return core_get_data(plugin_buffer_handle); } /* The plugin wants to stay resident after leaving its main function, e.g. diff --git a/apps/plugin.h b/apps/plugin.h index af673b3afe..38d8889d9e 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -112,6 +112,7 @@ void* plugin_get_buffer(size_t *buffer_size); #include "timefuncs.h" #include "crc32.h" #include "rbpaths.h" +#include "core_alloc.h" #ifdef HAVE_ALBUMART #include "albumart.h" diff --git a/apps/radio/radioart.c b/apps/radio/radioart.c index 76697c6017..cab6aa29ce 100644 --- a/apps/radio/radioart.c +++ b/apps/radio/radioart.c @@ -32,6 +32,7 @@ #include "kernel.h" #include "string-extra.h" #include "filefuncs.h" +#include "core_alloc.h" #define MAX_RADIOART_IMAGES 10 struct radioart { @@ -158,14 +159,49 @@ static void buffer_reset_handler(void *data) (void)data; } +static int shrink_callback(int handle, unsigned hints, void* start, size_t old_size) +{ + (void)start; + (void)old_size; + + ssize_t old_size_s = old_size; + size_t size_hint = (hints & BUFLIB_SHRINK_SIZE_MASK); + ssize_t wanted_size = old_size_s - size_hint; + + if (wanted_size <= 0) + { + core_free(handle); + buffering_reset(NULL, 0); + } + else + { + if (hints & BUFLIB_SHRINK_POS_FRONT) + start += size_hint; + + buffering_reset(start, wanted_size); + core_shrink(handle, start, wanted_size); + buf = start; + + /* one-shot */ + add_event(BUFFER_EVENT_BUFFER_RESET, true, buffer_reset_handler); + } + + return BUFLIB_CB_OK; +} + +static struct buflib_callbacks radioart_ops = { + .shrink_callback = shrink_callback, +}; + void radioart_init(bool entering_screen) { if (entering_screen) { /* grab control over buffering */ size_t bufsize; - buf = audio_get_buffer(false, &bufsize); - buffering_reset(buf, bufsize); + int handle = core_alloc_maximum("radioart", &bufsize, &radioart_ops); + buffering_reset(core_get_data(handle), bufsize); + buf = core_get_data(handle); /* one-shot */ add_event(BUFFER_EVENT_BUFFER_RESET, true, buffer_reset_handler); } diff --git a/apps/recorder/pcm_record.c b/apps/recorder/pcm_record.c index 2170e473bc..799c733948 100644 --- a/apps/recorder/pcm_record.c +++ b/apps/recorder/pcm_record.c @@ -24,6 +24,7 @@ #include "config.h" #include "system.h" #include "kernel.h" +#include "panic.h" #include "string-extra.h" #include "pcm_record.h" #include "codecs.h" @@ -40,6 +41,8 @@ #include "spdif.h" #endif #include "audio_thread.h" +#include "core_alloc.h" +#include "talk.h" /* Macros to enable logf for queues logging on SYS_TIMEOUT can be disabled */ @@ -1402,11 +1405,22 @@ static void tally_prerecord_data(void) /** Event handlers for recording thread **/ +static int pcmrec_handle; /* Q_AUDIO_INIT_RECORDING */ static void on_init_recording(void) { send_event(RECORDING_EVENT_START, NULL); - rec_buffer = audio_get_buffer(true, &rec_buffer_size); + /* dummy ops with no callbacks, needed because by + * default buflib buffers can be moved around which must be avoided + * FIXME: This buffer should play nicer and be shrinkable/movable */ + static struct buflib_callbacks dummy_ops; + talk_buffer_set_policy(TALK_BUFFER_LOOSE); + pcmrec_handle = core_alloc_maximum("pcmrec", &rec_buffer_size, &dummy_ops); + if (pcmrec_handle) + /* someone is abusing core_alloc_maximum(). Fix this evil guy instead of + * trying to handle OOM without hope */ + panicf("%s(): OOM\n", __func__); + rec_buffer = core_get_data(pcmrec_handle); init_rec_buffers(); init_state(); pcm_init_recording(); @@ -1430,6 +1444,10 @@ static void on_close_recording(void) audio_set_output_source(AUDIO_SRC_PLAYBACK); pcm_apply_settings(); + if (pcmrec_handle > 0) + pcmrec_handle = core_free(pcmrec_handle); + talk_buffer_set_policy(TALK_BUFFER_DEFAULT); + send_event(RECORDING_EVENT_STOP, NULL); } diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index 1c3460fbd0..4a7399b01e 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c @@ -691,15 +691,8 @@ void rec_set_source(int source, unsigned flags) void rec_set_recording_options(struct audio_recording_options *options) { -#if CONFIG_CODEC != SWCODEC - if (global_settings.rec_prerecord_time) - { - talk_buffer_steal(); /* will use the mp3 buffer */ - } -#else /* == SWCODEC */ rec_set_source(options->rec_source, options->rec_source_flags | SRCF_RECORDING); -#endif /* CONFIG_CODEC != SWCODEC */ audio_set_recording_options(options); } @@ -724,9 +717,6 @@ void rec_command(enum recording_command cmd) /* steal mp3 buffer, create unique filename and start recording */ pm_reset_clipcount(); pm_activate_clipcount(true); -#if CONFIG_CODEC != SWCODEC - talk_buffer_steal(); /* we use the mp3 buffer */ -#endif audio_record(rec_create_filename(path_buffer)); break; case RECORDING_CMD_START_NEWFILE: diff --git a/apps/talk.c b/apps/talk.c index b94dcf18ee..baf854fce3 100644 --- a/apps/talk.c +++ b/apps/talk.c @@ -79,8 +79,8 @@ const char* const file_thumbnail_ext = ".talk"; #define LOADED_MASK 0x80000000 /* MSB */ -/* swcodec: cap p_thumnail to MAX_THUMNAIL_BUFSIZE since audio keeps playing - * while voice +/* 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 @@ -95,25 +95,17 @@ struct clip_entry /* one entry of the index table */ int size; /* size of the clip */ }; -struct voicefile /* file format of our voice file */ +struct voicefile_header /* file format of our voice file */ { int version; /* version of the voicefile */ int target_id; /* the rockbox target the file was made for */ int table; /* offset to index table, (=header size) */ int id1_max; /* number of "normal" clips contained in above index */ int id2_max; /* number of "voice only" clips contained in above index */ - struct clip_entry index[]; /* followed by the index tables */ - /* and finally the mp3 clips, not visible here, bitswapped - for SH based players */ + /* The header is folled by the index tables (n*struct clip_entry), + * which is followed by the mp3/speex encoded clip data */ }; -struct queue_entry /* one entry of the internal queue */ -{ - unsigned char* buf; - long len; -}; - - /***************** Globals *****************/ #if (CONFIG_CODEC == SWCODEC && MEMORYSIZE <= 2) || defined(ONDIO_SERIES) @@ -125,7 +117,6 @@ struct queue_entry /* one entry of the internal queue */ #endif #ifdef TALK_PARTIAL_LOAD -static unsigned char *clip_buffer; static long max_clipsize; /* size of the biggest clip */ static long buffered_id[QUEUE_SIZE]; /* IDs of the talk clips */ static uint8_t clip_age[QUEUE_SIZE]; @@ -134,16 +125,13 @@ static uint8_t clip_age[QUEUE_SIZE]; #endif #endif -static char* voicebuf; /* root pointer to our buffer */ -static unsigned char* p_thumbnail = NULL; /* buffer for thumbnails */ /* 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* p_voicefile; /* loaded voicefile */ +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 */ -static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ static bool force_enqueue_next; /* enqueue next utterance even if enqueue is false */ static int queue_write; /* write index of queue, by application */ static int queue_read; /* read index of queue, by ISR context */ @@ -158,18 +146,126 @@ static struct mutex queue_mutex SHAREDBSS_ATTR; #endif /* CONFIG_CODEC */ static int sent; /* how many bytes handed over to playback, owned by ISR */ static unsigned char curr_hd[3]; /* current frame header, for re-sync */ -static int filehandle = -1; /* global, so we can keep the file open if needed */ -static unsigned char* p_silence; /* VOICE_PAUSE clip, used for termination */ -static long silence_len; /* length of the VOICE_PAUSE clip */ -static unsigned char* p_lastclip; /* address of latest clip, for silence add */ -static unsigned long voicefile_size = 0; /* size of the loaded voice file */ +static int silence_offset; /* VOICE_PAUSE clip, used for termination */ +static long silence_length; /* length of the VOICE_PAUSE clip */ +static unsigned long lastclip_offset; /* address of latest clip, for silence add */ static unsigned char last_lang[MAX_FILENAME+1]; /* name of last used lang file (in talk_init) */ static bool talk_initialized; /* true if talk_init has been called */ +static bool give_buffer_away; /* true if we should give the buffers away in shrink_callback if requested */ static int talk_temp_disable_count; /* if positive, temporarily disable voice UI (not saved) */ + /* size of the loaded voice file + * offsets smaller than this denote a clip from teh voice file, + * offsets larger than this denote a thumbnail clip */ +static unsigned long voicefile_size; + +struct queue_entry /* one entry of the internal queue */ +{ + int offset, length; +}; + +static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ /***************** Private implementation *****************/ +static int thumb_handle; +static int talk_handle, talk_handle_locked; + +#if CONFIG_CODEC != SWCODEC + +/* on HWCODEC only voice xor audio can be active at a time */ +static bool check_audio_status(void) +{ + if (audio_status()) /* busy, buffer in use */ + return false; + /* ensure playback is given up on the buffer */ + audio_hard_stop(); + return true; +} + +/* ISR (mp3_callback()) must not run during moving of the clip buffer, + * because the MAS may get out-of-sync */ +static void sync_callback(int handle, bool sync_on) +{ + (void) handle; + (void) sync_on; +#if CONFIG_CPU == SH7034 + if (sync_on) + CHCR3 &= ~0x0001; /* disable the DMA (and therefore the interrupt also) */ + else + CHCR3 |= 0x0001; /* re-enable the DMA */ +#endif +} +#else +#define check_audio_status() (true) +#endif + +static int move_callback(int handle, void *current, void *new) +{ + (void)handle;(void)current;(void)new; + if (UNLIKELY(talk_handle_locked)) + return BUFLIB_CB_CANNOT_MOVE; + return BUFLIB_CB_OK; +} + +static int shrink_callback(int handle, unsigned hints, void *start, size_t old_size) +{ + (void)start;(void)old_size; + int *h; + if (handle == talk_handle) + h = &talk_handle; + else // if (handle == thumb_handle) + h = &thumb_handle; + + if (LIKELY(!talk_handle_locked) + && give_buffer_away + && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) + { + *h = core_free(handle); + return BUFLIB_CB_OK; + } + return BUFLIB_CB_CANNOT_SHRINK; +} + +static struct buflib_callbacks talk_ops = { + .move_callback = move_callback, +#if CONFIG_CODEC != SWCODEC + .sync_callback = sync_callback, +#endif + .shrink_callback = shrink_callback, +}; + + +static int index_handle, index_handle_locked; +static int index_move_callback(int handle, void *current, void *new) +{ + (void)handle;(void)current;(void)new; + if (UNLIKELY(index_handle_locked)) + return BUFLIB_CB_CANNOT_MOVE; + return BUFLIB_CB_OK; +} + +static int index_shrink_callback(int handle, unsigned hints, void *start, size_t old_size) +{ + (void)start;(void)old_size; + if (LIKELY(!index_handle_locked) + && give_buffer_away + && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) + { + index_handle = core_free(handle); + /* the clip buffer isn't usable without index table */ + if (LIKELY(!talk_handle_locked)) + talk_handle = core_free(talk_handle); + return BUFLIB_CB_OK; + } + return BUFLIB_CB_CANNOT_SHRINK; +} + +static struct buflib_callbacks index_ops = { + .move_callback = index_move_callback, + .shrink_callback = index_shrink_callback, +}; + static int open_voicefile(void) { char buf[64]; @@ -188,38 +284,41 @@ static int open_voicefile(void) /* fetch a clip from the voice file */ -static unsigned char* get_clip(long id, long* p_size) +static int get_clip(long id, long* p_size) { - long clipsize; - unsigned char* clipbuf; - + int retval = -1; + struct clip_entry* clipbuf; + size_t clipsize; + if (id > VOICEONLY_DELIMITER) { /* voice-only entries use the second part of the table. The first string comes after VOICEONLY_DELIMITER so we need to substract VOICEONLY_DELIMITER + 1 */ id -= VOICEONLY_DELIMITER + 1; - if (id >= p_voicefile->id2_max) - return NULL; /* must be newer than we have */ - id += p_voicefile->id1_max; /* table 2 is behind table 1 */ + if (id >= voicefile.id2_max) + return -1; /* must be newer than we have */ + id += voicefile.id1_max; /* table 2 is behind table 1 */ } else { /* normal use of the first table */ - if (id >= p_voicefile->id1_max) - return NULL; /* must be newer than we have */ + if (id >= voicefile.id1_max) + return -1; /* must be newer than we have */ } - - clipsize = p_voicefile->index[id].size; + + clipbuf = core_get_data(index_handle); + clipsize = clipbuf[id].size; if (clipsize == 0) /* clip not included in voicefile */ - return NULL; + return -1; #ifndef TALK_PARTIAL_LOAD - clipbuf = (unsigned char *) p_voicefile + p_voicefile->index[id].offset; -#endif + retval = clipbuf[id].offset; -#ifdef TALK_PARTIAL_LOAD +#else if (!(clipsize & LOADED_MASK)) { /* clip needs loading */ - int idx = 0; + ssize_t ret; + int fd, idx = 0; + unsigned char *voicebuf; if (id == VOICE_PAUSE) { idx = QUEUE_SIZE; /* we keep VOICE_PAUSE loaded */ } else { @@ -243,18 +342,29 @@ static unsigned char* get_clip(long id, long* p_size) } clip_age[idx] = 0; /* reset clip's age */ } - clipbuf = clip_buffer + idx * max_clipsize; + retval = idx * max_clipsize; + fd = open_voicefile(); + if (fd < 0) + return -1; /* open error */ - lseek(filehandle, p_voicefile->index[id].offset, SEEK_SET); - if (read(filehandle, clipbuf, clipsize) != clipsize) - return NULL; /* read error */ + talk_handle_locked++; + voicebuf = core_get_data(talk_handle); + clipbuf = core_get_data(index_handle); + lseek(fd, clipbuf[id].offset, SEEK_SET); + ret = read(fd, &voicebuf[retval], clipsize); + close(fd); + talk_handle_locked--; - p_voicefile->index[id].size |= LOADED_MASK; /* mark as loaded */ + if (ret < 0 || clipsize != (size_t)ret) + return -1; /* read error */ + + clipbuf = core_get_data(index_handle); + clipbuf[id].size |= LOADED_MASK; /* mark as loaded */ if (id != VOICE_PAUSE) { if (buffered_id[idx] >= 0) { /* mark previously loaded clip as unloaded */ - p_voicefile->index[buffered_id[idx]].size &= ~LOADED_MASK; + clipbuf[buffered_id[idx]].size &= ~LOADED_MASK; } buffered_id[idx] = id; } @@ -262,14 +372,13 @@ static unsigned char* get_clip(long id, long* p_size) else { /* clip is in memory already */ /* Find where it was loaded */ - clipbuf = clip_buffer; if (id == VOICE_PAUSE) { - clipbuf += QUEUE_SIZE * max_clipsize; + retval = QUEUE_SIZE * max_clipsize; } else { int idx; for (idx=0; idx 0) /* nothing to do? */ + return true; + + ssize_t alloc_size = (hdr->id1_max + hdr->id2_max) * sizeof(struct clip_entry); + index_handle = core_alloc_ex("voice index", alloc_size, &index_ops); + if (index_handle < 0) + return false; + + index_handle_locked++; + buf = core_get_data(index_handle); + ret = read(fd, buf, alloc_size); + +#ifndef TALK_PARTIAL_LOAD + int clips_offset, num_clips; + /* adjust the offsets of the clips, they are relative to the file + * TALK_PARTUAL_LOAD needs the file offset instead as it loads + * the clips later */ + clips_offset = hdr->table; + num_clips = hdr->id1_max + hdr->id2_max; + clips_offset += num_clips * sizeof(struct clip_entry); /* skip index */ +#endif + if (ret == alloc_size) + for (int i = 0; i < hdr->id1_max + hdr->id2_max; i++) + { #ifdef ROCKBOX_LITTLE_ENDIAN - int i; + structec_convert(&buf[i], "ll", 1, true); #endif - - if (!probe) - filehandle = open_voicefile(); - if (filehandle < 0) /* failed to open */ - goto load_err; - - voicebuf.buf = buf; - if (!voicebuf.buf) - goto load_err; - -#ifdef TALK_PARTIAL_LOAD - /* load only the header for now */ - load_size = sizeof(struct voicefile); -#else - /* load the entire file */ - load_size = filesize(filehandle); +#ifndef TALK_PARTIAL_LOAD + buf[i].offset -= clips_offset; #endif - if (load_size > bufsize) /* won't fit? */ - goto load_err; + } - got_size = read(filehandle, voicebuf.buf, load_size); - if (got_size != (ssize_t)load_size /* failure */) - goto load_err; + index_handle_locked--; - alloc_size = load_size; + if (ret != alloc_size) + index_handle = core_free(index_handle); + + return ret == alloc_size; +} + +static bool load_header(int fd, struct voicefile_header *hdr) +{ + ssize_t got_size = read(fd, hdr, sizeof(*hdr)); + if (got_size != sizeof(*hdr)) + return false; #ifdef ROCKBOX_LITTLE_ENDIAN logf("Byte swapping voice file"); - structec_convert(voicebuf.buf, "lllll", 1, true); + structec_convert(&voicefile, "lllll", 1, true); #endif + return true; +} + +#ifndef TALK_PARTIAL_LOAD +static bool load_data(int fd, ssize_t size_to_read) +{ + unsigned char *buf; + ssize_t ret; + + if (size_to_read < 0) + return false; + + talk_handle = core_alloc_ex("voice data", size_to_read, &talk_ops); + if (talk_handle < 0) + return false; + + talk_handle_locked++; + buf = core_get_data(talk_handle); + ret = read(fd, buf, size_to_read); + talk_handle_locked--; + + if (ret != size_to_read) + talk_handle = core_free(talk_handle); + + return ret == size_to_read; +} +#endif + +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, &talk_ops); + if (handle < 0) + { + size = core_allocatable(); + handle = core_alloc_ex("voice thumb", size, &talk_ops); + } +#else + /* on HWCODEC, just use the rest of the remaining buffer, + * normal playback cannot happen anyway */ + handle = core_alloc_maximum("voice thumb", &size, &talk_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) +{ + if (fd < 0) /* failed to open */ + return false; + + /* load the header first */ + if (!load_header(fd, &voicefile)) + return false; /* format check */ - if (voicebuf.file->table == sizeof(struct voicefile)) + if (voicefile.table == sizeof(struct voicefile_header)) { - p_voicefile = voicebuf.file; - - if (p_voicefile->version != VOICE_VERSION || - p_voicefile->target_id != TARGET_ID) + if (voicefile.version == VOICE_VERSION && + voicefile.target_id == TARGET_ID) { - logf("Incompatible voice file"); - goto load_err; + if (load_index_table(fd, &voicefile)) + return true; } } - else - goto load_err; + logf("Incompatible voice file"); + return false; +} + +static bool load_voicefile_data(int fd, size_t max_size) +{ #ifdef TALK_PARTIAL_LOAD - /* load the index table, now that we know its size from the header */ - load_size = (p_voicefile->id1_max + p_voicefile->id2_max) - * sizeof(struct clip_entry); - - if (load_size > bufsize) /* won't fit? */ - goto load_err; - - got_size = read(filehandle, &p_voicefile->index[0], load_size); - if (got_size != (ssize_t)load_size) /* read error */ - goto load_err; - - alloc_size += load_size; + /* just allocate, populate on an as-needed basis later */ + talk_handle = core_alloc_ex("voice data", max_size, &talk_ops); + if (talk_handle < 0) + goto load_err_free; #else - close(filehandle); - filehandle = -1; -#endif /* TALK_PARTIAL_LOAD */ - -#ifdef ROCKBOX_LITTLE_ENDIAN - for (i = 0; i < p_voicefile->id1_max + p_voicefile->id2_max; i++) - structec_convert(&p_voicefile->index[i], "ll", 1, true); + size_t load_size, clips_size; + /* load the entire file into memory */ + clips_size = (voicefile.id1_max+voicefile.id2_max) * sizeof(struct clip_entry); + load_size = max_size - voicefile.table - clips_size; + if (!load_data(fd, load_size)) + goto load_err_free; #endif -#ifdef TALK_PARTIAL_LOAD - clip_buffer = (unsigned char *) p_voicefile + p_voicefile->table; - unsigned clips = p_voicefile->id1_max + p_voicefile->id2_max; - clip_buffer += clips * sizeof(struct clip_entry); /* skip index */ -#endif - if (!probe) { - /* make sure to have the silence clip, if available */ - p_silence = get_clip(VOICE_PAUSE, &silence_len); - } + /* make sure to have the silence clip, if available + * return value can be cached globally even for TALK_PARTIAL_LOAD because + * the VOICE_PAUSE clip is specially handled */ + silence_offset = get_clip(VOICE_PAUSE, &silence_length); -#ifdef TALK_PARTIAL_LOAD - alloc_size += silence_len + QUEUE_SIZE; -#endif + /* not an error if this fails here, might try again when the + * actual thumbnails are attempted to be played back */ + alloc_thumbnail_buf(); - if (alloc_size > bufsize) - goto load_err; + return true; - /* now move p_thumbnail behind the voice clip buffer */ - p_thumbnail = voicebuf.buf + alloc_size; - p_thumbnail += (long)p_thumbnail % 2; /* 16-bit align */ - size_for_thumbnail = voicebuf.buf + bufsize - p_thumbnail; -#if CONFIG_CODEC == SWCODEC - size_for_thumbnail = MIN(size_for_thumbnail, MAX_THUMBNAIL_BUFSIZE); -#endif - if (size_for_thumbnail <= 0) - p_thumbnail = NULL; - - return; -load_err: - p_voicefile = NULL; - has_voicefile = false; /* don't try again */ - if (filehandle >= 0) - { - close(filehandle); - filehandle = -1; - } - return; +load_err_free: + index_handle = core_free(index_handle); + return false; } /* called in ISR context (on HWCODEC) if mp3 data got consumed */ static void mp3_callback(const void** start, size_t* size) { - queue[queue_read].len -= sent; /* we completed this */ - queue[queue_read].buf += sent; + queue[queue_read].length -= sent; /* we completed this */ + queue[queue_read].offset += sent; - if (queue[queue_read].len > 0) /* current clip not finished? */ + if (queue[queue_read].length > 0) /* current clip not finished? */ { /* feed the next 64K-1 chunk */ + int offset; #if CONFIG_CODEC != SWCODEC - sent = MIN(queue[queue_read].len, 0xFFFF); + sent = MIN(queue[queue_read].length, 0xFFFF); #else - sent = queue[queue_read].len; + sent = queue[queue_read].length; #endif - *start = queue[queue_read].buf; + offset = queue[queue_read].offset; + if ((unsigned long)offset >= voicefile_size) + *start = core_get_data(thumb_handle) + offset - voicefile_size; + else + *start = core_get_data(talk_handle) + offset; *size = sent; return; } talk_queue_lock(); - if(p_thumbnail - && queue[queue_read].buf == p_thumbnail +thumbnail_buf_used) + if(thumb_handle && (unsigned long)queue[queue_read].offset == voicefile_size+thumbnail_buf_used) thumbnail_buf_used = 0; if (sent > 0) /* go to next entry */ { @@ -436,24 +592,31 @@ re_check: if (QUEUE_LEVEL != 0) /* queue is not empty? */ { /* start next clip */ + unsigned char *buf; #if CONFIG_CODEC != SWCODEC - sent = MIN(queue[queue_read].len, 0xFFFF); + sent = MIN(queue[queue_read].length, 0xFFFF); #else - sent = queue[queue_read].len; + sent = queue[queue_read].length; #endif - *start = p_lastclip = queue[queue_read].buf; + lastclip_offset = queue[queue_read].offset; + /* offsets larger than voicefile_size denote thumbnail clips */ + if (lastclip_offset >= voicefile_size) + buf = core_get_data(thumb_handle) + lastclip_offset - voicefile_size; + else + buf = core_get_data(talk_handle) + lastclip_offset; + *start = buf; *size = sent; - curr_hd[0] = p_lastclip[1]; - curr_hd[1] = p_lastclip[2]; - curr_hd[2] = p_lastclip[3]; + curr_hd[0] = buf[1]; + curr_hd[1] = buf[2]; + curr_hd[2] = buf[3]; } - else if (p_silence != NULL /* silence clip available */ - && p_lastclip != p_silence /* previous clip wasn't silence */ - && !(p_lastclip >= p_thumbnail /* ..or thumbnail */ - && p_lastclip < p_thumbnail +size_for_thumbnail)) + else if (silence_offset > 0 /* silence clip available */ + && lastclip_offset != (unsigned long)silence_offset /* previous clip wasn't silence */ + && !(lastclip_offset >= voicefile_size /* ..or thumbnail */ + && lastclip_offset < voicefile_size +size_for_thumbnail)) { /* add silence clip when queue runs empty playing a voice clip */ - queue[queue_write].buf = p_silence; - queue[queue_write].len = silence_len; + queue[queue_write].offset = silence_offset; + queue[queue_write].length = silence_length; queue_write = (queue_write + 1) & QUEUE_MASK; goto re_check; @@ -461,6 +624,7 @@ re_check: else { *size = 0; /* end of data */ + talk_handle_locked--; } talk_queue_unlock(); } @@ -478,6 +642,8 @@ void talk_force_shutup(void) unsigned char* pos; unsigned char* search; unsigned char* end; + int len; + unsigned clip_offset; if (QUEUE_LEVEL == 0) /* has ended anyway */ return; @@ -486,13 +652,17 @@ void talk_force_shutup(void) #endif /* CONFIG_CPU == SH7034 */ /* search next frame boundary and continue up to there */ pos = search = mp3_get_pos(); - end = queue[queue_read].buf + queue[queue_read].len; + clip_offset = queue[queue_read].offset; + if (clip_offset >= voicefile_size) + end = core_get_data(thumb_handle) + clip_offset - voicefile_size; + else + end = core_get_data(talk_handle) + clip_offset; + len = queue[queue_read].length; - if (pos >= queue[queue_read].buf - && pos <= end) /* really our clip? */ + if (pos >= end && pos <= (end+len)) /* really our clip? */ { /* (for strange reasons this isn't nesessarily the case) */ /* find the next frame boundary */ - while (search < end) /* search the remaining data */ + while (search < (end+len)) /* search the remaining data */ { if (*search++ != 0xFF) /* quick search for frame sync byte */ continue; /* (this does the majority of the job) */ @@ -512,7 +682,7 @@ void talk_force_shutup(void) sent = search-pos; queue_write = (queue_read + 1) & QUEUE_MASK; /* will be empty after next callback */ - queue[queue_read].len = sent; /* current one ends after this */ + queue[queue_read].length = sent; /* current one ends after this */ #if CONFIG_CPU == SH7034 DTCR3 = sent; /* let the DMA finish this frame */ @@ -528,6 +698,7 @@ void talk_force_shutup(void) mp3_play_stop(); talk_queue_lock(); queue_write = queue_read = 0; /* reset the queue */ + talk_handle_locked = MAX(talk_handle_locked-1, 0); thumbnail_buf_used = 0; talk_queue_unlock(); need_shutup = false; @@ -541,8 +712,9 @@ void talk_shutup(void) } /* schedule a clip, at the end or discard the existing queue */ -static void queue_clip(unsigned char* buf, long size, bool enqueue) +static void queue_clip(unsigned long clip_offset, long size, bool enqueue) { + unsigned char *buf; int queue_level; if (!enqueue) @@ -562,20 +734,25 @@ static void queue_clip(unsigned char* buf, long size, bool enqueue) if (queue_level < QUEUE_SIZE - 1) /* space left? */ { - queue[queue_write].buf = buf; /* populate an entry */ - queue[queue_write].len = size; + queue[queue_write].offset = clip_offset; /* populate an entry */ + queue[queue_write].length = size; queue_write = (queue_write + 1) & QUEUE_MASK; } talk_queue_unlock(); if (queue_level == 0) { /* queue was empty, we have to do the initial start */ - p_lastclip = buf; + lastclip_offset = clip_offset; #if CONFIG_CODEC != SWCODEC sent = MIN(size, 0xFFFF); /* DMA can do no more */ #else sent = size; #endif + talk_handle_locked++; + if (clip_offset >= voicefile_size) + buf = core_get_data(thumb_handle) + clip_offset - voicefile_size; + else + buf = core_get_data(talk_handle) + clip_offset; mp3_play_data(buf, sent, mp3_callback); curr_hd[0] = buf[1]; curr_hd[1] = buf[2]; @@ -594,53 +771,11 @@ static void queue_clip(unsigned char* buf, long size, bool enqueue) return; } -static void alloc_thumbnail_buf(void) -{ - /* use the audio buffer now, need to release before loading a voice */ - p_thumbnail = voicebuf; -#if CONFIG_CODEC == SWCODEC - size_for_thumbnail = MAX_THUMBNAIL_BUFSIZE; -#endif - thumbnail_buf_used = 0; -} - -/* common code for talk_init() and talk_buffer_steal() */ -static void reset_state(void) -{ - queue_write = queue_read = 0; /* reset the queue */ - p_voicefile = NULL; /* indicate no voicefile (trashed) */ - p_thumbnail = NULL; /* no thumbnails either */ - -#ifdef TALK_PARTIAL_LOAD - int i; - for(i=0; i= 0) - { - close(filehandle); - filehandle = -1; - } -#endif - #if CONFIG_CODEC == SWCODEC if(!talk_initialized) mutex_init(&queue_mutex); @@ -665,138 +792,118 @@ void talk_init(void) strlcpy((char *)last_lang, (char *)global_settings.lang_file, MAX_FILENAME); - filehandle = open_voicefile(); - if (filehandle < 0) { - has_voicefile = false; - voicefile_size = 0; - return; - } - - voicefile_size = filesize(filehandle); - - audio_get_buffer(false, NULL); /* Must tell audio to reinitialize */ - reset_state(); /* use this for most of our inits */ + /* reset some states */ + queue_write = queue_read = 0; /* reset the queue */ + memset(&voicefile, 0, sizeof(voicefile)); #ifdef TALK_PARTIAL_LOAD - size_t bufsize; - char* buf = plugin_get_buffer(&bufsize); - /* we won't load the full file, we only need the index */ - load_voicefile(true, buf, bufsize); - if (!p_voicefile) + for(int i=0; i 0) index_handle = core_free(index_handle); + if (talk_handle > 0) talk_handle = core_free(talk_handle); + /* don't free thumb handle, it doesn't depend on the actual voice file + * and so we can re-use it if it's already allocated in any event */ + + filehandle = open_voicefile(); + if (filehandle < 0) return; - unsigned clips = p_voicefile->id1_max + p_voicefile->id2_max; - unsigned i; + if (!load_voicefile_index(filehandle)) + goto out; + +#ifdef TALK_PARTIAL_LOAD + /* TALK_PARTIAL_LOAD loads the actual clip data later, and not all + * at once */ + unsigned num_clips = voicefile.id1_max + voicefile.id2_max; + struct clip_entry *clips = core_get_data(index_handle); int silence_size = 0; - for(i=0; iindex[i].size; + for(unsigned i=0; i max_clipsize) max_clipsize = size; if (i == VOICE_PAUSE) silence_size = size; } - voicefile_size = p_voicefile->table + clips * sizeof(struct clip_entry); + voicefile_size = voicefile.table + num_clips * sizeof(struct clip_entry); voicefile_size += max_clipsize * QUEUE_SIZE + silence_size; - p_voicefile = NULL; /* Don't pretend we can load talk clips just yet */ -#endif - /* test if we can open and if it fits in the audiobuffer */ size_t audiobufsz = audio_buffer_available(); - if (voicefile_size <= audiobufsz) { - has_voicefile = true; - } else { - has_voicefile = false; - voicefile_size = 0; - } + has_voicefile = audiobufsz >= voicefile_size; - close(filehandle); /* close again, this was just to detect presence */ - filehandle = -1; +#else + /* load the compressed clip data into memory, in its entirety */ + voicefile_size = filesize(filehandle); + if (!load_voicefile_data(filehandle, voicefile_size)) + { + voicefile_size = 0; + goto out; + } + has_voicefile = true; +#endif #if CONFIG_CODEC == SWCODEC /* Safe to init voice playback engine now since we now know if talk is required or not */ voice_thread_init(); #endif + +out: + close(filehandle); /* close again, this was just to detect presence */ + filehandle = -1; } #if CONFIG_CODEC == SWCODEC /* return if a voice codec is required or not */ bool talk_voice_required(void) { - return (voicefile_size != 0) /* Voice file is available */ + return (has_voicefile) /* Voice file is available */ || (global_settings.talk_dir_clip) /* Thumbnail clips are required */ || (global_settings.talk_file_clip); } #endif -/* return size of voice file */ -static size_t talk_get_buffer_size(void) -{ -#if CONFIG_CODEC == SWCODEC - return voicefile_size + MAX_THUMBNAIL_BUFSIZE; -#else - return audio_buffer_available(); -#endif -} - -/* Sets the buffer for the voicefile and returns how many bytes of this - * buffer we will use for the voicefile */ -size_t talkbuf_init(char *bufstart) -{ - bool changed = voicebuf != bufstart; - - if (changed) /* must reload voice file */ - reset_state(); - - if (bufstart) - voicebuf = bufstart; - - return talk_get_buffer_size(); -} - /* somebody else claims the mp3 buffer, e.g. for regular play/record */ -void talk_buffer_steal(void) +void talk_buffer_set_policy(int policy) { -#if CONFIG_CODEC != SWCODEC - mp3_play_stop(); -#endif -#if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) - if (filehandle >= 0) + switch(policy) { - close(filehandle); - filehandle = -1; + case TALK_BUFFER_DEFAULT: + case TALK_BUFFER_HOLD: give_buffer_away = false; break; + case TALK_BUFFER_LOOSE: give_buffer_away = true; break; + default: DEBUGF("Ignoring unknown policy\n"); break; } -#endif - reset_state(); } /* play a voice ID from voicefile */ int talk_id(int32_t id, bool enqueue) { + int clip; long clipsize; - unsigned char* clipbuf; int32_t unit; int decimals; if (talk_temp_disable_count > 0) return -1; /* talking has been disabled */ -#if CONFIG_CODEC == SWCODEC - /* If talk buffer was stolen, it must be restored for voicefile's sake */ - if (!restore_state()) - return -1; /* cannot get any space */ -#else - if (audio_status()) /* busy, buffer in use */ + if (!check_audio_status()) return -1; -#endif - if (p_voicefile == NULL && has_voicefile) /* reload needed? */ - load_voicefile(false, voicebuf, talk_get_buffer_size()); - - if (p_voicefile == NULL) /* still no voices? */ - return -1; + if (has_voicefile && (talk_handle <= 0 || index_handle <= 0)) /* reload needed? */ + { + int fd = open_voicefile(); + if (fd < 0 + || !load_voicefile_index(fd) + || !load_voicefile_data(fd, voicefile_size)) + return -1; + } if (id == -1) /* -1 is an indication for silence */ return -1; @@ -814,8 +921,8 @@ int talk_id(int32_t id, bool enqueue) return 0; /* and stop, end of special case */ } - clipbuf = get_clip(id, &clipsize); - if (clipbuf == NULL) + clip = get_clip(id, &clipsize); + if (clip < 0) return -1; /* not present */ #ifdef LOGF_ENABLE @@ -825,7 +932,7 @@ int talk_id(int32_t id, bool enqueue) logf("\ntalk_id: Say '%s'\n", str(id)); #endif - queue_clip(clipbuf, clipsize, enqueue); + queue_clip(clip, clipsize, enqueue); return 0; } @@ -859,23 +966,18 @@ static int _talk_file(const char* filename, int fd; int size; int thumb_used; + char *buf; #if CONFIG_CODEC != SWCODEC struct mp3entry info; #endif if (talk_temp_disable_count > 0) return -1; /* talking has been disabled */ -#if CONFIG_CODEC == SWCODEC - /* If talk buffer was stolen, it must be restored for thumbnail's sake */ - if (!restore_state()) - return -1; /* cannot get any space */ -#else - if (audio_status()) /* busy, buffer in use */ - return -1; -#endif + if (!check_audio_status()) + return -1; - if (p_thumbnail == NULL || size_for_thumbnail <= 0) - alloc_thumbnail_buf(); + if (!alloc_thumbnail_buf()) + return -1; #if CONFIG_CODEC != SWCODEC if(mp3info(&info, filename)) /* use this to find real start */ @@ -905,8 +1007,10 @@ static int _talk_file(const char* filename, lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ #endif - size = read(fd, p_thumbnail +thumb_used, - size_for_thumbnail -thumb_used); + talk_handle_locked++; + buf = core_get_data(thumb_handle); + size = read(fd, buf+thumb_used, size_for_thumbnail - thumb_used); + talk_handle_locked--; close(fd); /* ToDo: find audio, skip ID headers and trailers */ @@ -914,7 +1018,8 @@ static int _talk_file(const char* filename, if (size > 0) /* Don't play missing clips */ { #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) - bitswap(p_thumbnail, size); + /* bitswap doesnt yield() */ + bitswap(core_get_data(thumb_handle), size); #endif if(prefix_ids) /* prefix thumbnail by speaking these ids, but only now @@ -922,9 +1027,9 @@ static int _talk_file(const char* filename, spoken. */ talk_idarray(prefix_ids, true); talk_queue_lock(); - thumbnail_buf_used = thumb_used +size; + thumbnail_buf_used = thumb_used + size; talk_queue_unlock(); - queue_clip(p_thumbnail +thumb_used, size, true); + queue_clip(voicefile_size + thumb_used, size, true); } return size; @@ -1012,10 +1117,8 @@ int talk_number(long n, bool enqueue) if (talk_temp_disable_count > 0) return -1; /* talking has been disabled */ -#if CONFIG_CODEC != SWCODEC - if (audio_status()) /* busy, buffer in use */ - return -1; -#endif + if (!check_audio_status()) + return -1; if (!enqueue) talk_shutup(); /* cut off all the pending stuff */ @@ -1160,10 +1263,8 @@ int talk_value_decimal(long n, int unit, int decimals, bool enqueue) if (talk_temp_disable_count > 0) return -1; /* talking has been disabled */ -#if CONFIG_CODEC != SWCODEC - if (audio_status()) /* busy, buffer in use */ - return -1; -#endif + if (!check_audio_status()) + return -1; /* special case for time duration */ if (unit == UNIT_TIME) @@ -1217,10 +1318,8 @@ int talk_spell(const char* spell, bool enqueue) if (talk_temp_disable_count > 0) return -1; /* talking has been disabled */ -#if CONFIG_CODEC != SWCODEC - if (audio_status()) /* busy, buffer in use */ - return -1; -#endif + if (!check_audio_status()) + return -1; if (!enqueue) talk_shutup(); /* cut off all the pending stuff */ diff --git a/apps/talk.h b/apps/talk.h index 55e7208f1d..28be71884f 100644 --- a/apps/talk.h +++ b/apps/talk.h @@ -71,6 +71,26 @@ enum { /* convenience macro to have both virtual pointer and ID as arguments */ #define STR(id) ID2P(id), id +/* Policy values for how hard to try to keep the talk/voice buffers. + * Affects how genereous talk.c is when it's asked for memory in + * shrink_callbacks(). + * + * I.e. setting the policy to TALK_BUFFER_LOOSE, it will happily give its + * entire bufer away if asked for, e.g. due to a another module + * calling core_alloc_maximum(), TALK_BUFFER_HOLD on the other hand will + * make it keep the buffers so that a call to core_alloc_maximum() does not + * stop the speech-interface. + */ +enum talk_buffer_policies { + TALK_BUFFER_DEFAULT, + TALK_BUFFER_LOOSE, + TALK_BUFFER_HOLD, +}; + +/* This sets the actual policy. Call this before core_alloc_maximum() to + * get the desired outcome */ +void talk_buffer_set_policy(int policy); + /* publish these strings, so they're stored only once (better than #define) */ extern const char* const dir_thumbnail_name; /* "_dirname.talk" */ extern const char* const file_thumbnail_ext; /* ".talk" for file voicing */ @@ -81,7 +101,6 @@ bool talk_voice_required(void); /* returns true if voice codec required */ #endif int talk_get_bufsize(void); /* get the loaded voice file size */ size_t talkbuf_init(char* bufstart); -void talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */ bool is_voice_queued(void); /* Are there more voice clips to be spoken? */ int talk_id(int32_t id, bool enqueue); /* play a voice ID from voicefont */ /* play a thumbnail from file */ diff --git a/firmware/export/audio.h b/firmware/export/audio.h index 8108f50939..57f7981b34 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -75,10 +75,6 @@ void audio_error_clear(void); int audio_get_file_pos(void); void audio_beep(int duration); -/* Required call when audio buffer is required for some other purpose */ -/* implemented in apps but called from firmware(!) */ -unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size); - #if CONFIG_CODEC == SWCODEC void audio_next_dir(void); void audio_prev_dir(void); diff --git a/firmware/target/arm/pp/usb-fw-pp502x.c b/firmware/target/arm/pp/usb-fw-pp502x.c index 44cce14389..acbb221cfd 100644 --- a/firmware/target/arm/pp/usb-fw-pp502x.c +++ b/firmware/target/arm/pp/usb-fw-pp502x.c @@ -230,21 +230,6 @@ void usb_insert_int(void) } #endif /* USB_STATUS_BY_EVENT */ -#ifdef HAVE_BOOTLOADER_USB_MODE -/* Replacement function that returns all unused memory after the bootloader - * because the storage driver uses the audio buffer */ -extern unsigned char freebuffer[]; -extern unsigned char freebufferend[]; -unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size) -{ - if (buffer_size) - *buffer_size = freebufferend - freebuffer + 1; - - return freebuffer; - (void)talk_buf; -} -#endif /* HAVE_BOOTLOADER_USB_MODE */ - void usb_drv_int_enable(bool enable) { /* enable/disable USB IRQ in CPU */ diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c index d1279d0ee1..a3b867319d 100644 --- a/firmware/usbstack/usb_storage.c +++ b/firmware/usbstack/usb_storage.c @@ -34,6 +34,7 @@ #if CONFIG_RTC #include "timefuncs.h" #endif +#include "core_alloc.h" #ifdef USB_USE_RAMDISK #define RAMDISK_SIZE 2048 @@ -430,6 +431,7 @@ int usb_storage_get_config_descriptor(unsigned char *dest,int max_packet_size) return (dest - orig_dest); } +static int usb_handle; void usb_storage_init_connection(void) { logf("ums: set config"); @@ -452,13 +454,17 @@ void usb_storage_init_connection(void) #else /* TODO : check if bufsize is at least 32K ? */ size_t bufsize; - unsigned char * audio_buffer; + unsigned char * buffer; + /* dummy ops with no callbacks, needed because by + * default buflib buffers can be moved around which must be avoided */ + static struct buflib_callbacks dummy_ops; - audio_buffer = audio_get_buffer(false,&bufsize); + usb_handle = core_alloc_maximum("usb storage", &bufsize, &dummy_ops); + buffer = core_get_data(usb_handle); #if defined(UNCACHED_ADDR) && CONFIG_CPU != AS3525 - cbw_buffer = (void *)UNCACHED_ADDR((unsigned int)(audio_buffer+31) & 0xffffffe0); + cbw_buffer = (void *)UNCACHED_ADDR((unsigned int)(buffer+31) & 0xffffffe0); #else - cbw_buffer = (void *)((unsigned int)(audio_buffer+31) & 0xffffffe0); + cbw_buffer = (void *)((unsigned int)(buffer+31) & 0xffffffe0); #endif tb.transfer_buffer = cbw_buffer + MAX_CBW_SIZE; commit_discard_dcache(); @@ -478,7 +484,8 @@ void usb_storage_init_connection(void) void usb_storage_disconnect(void) { - /* Empty for now */ + if (usb_handle > 0) + usb_handle = core_free(usb_handle); } /* called by usb_core_transfer_complete() */