From 3cc46e762065c1d7f6bceebaa39572e30ca78b2b Mon Sep 17 00:00:00 2001 From: Steve Bavin Date: Fri, 1 Sep 2006 07:59:31 +0000 Subject: Rearrangement of playback.c to group routines by thread git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10838 a1c6a512-1295-4272-9138-f99709370657 --- apps/playback.c | 4593 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 2307 insertions(+), 2286 deletions(-) (limited to 'apps') diff --git a/apps/playback.c b/apps/playback.c index ed8bb10caa..352c99b390 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -165,27 +165,6 @@ extern bool audio_is_initialized; static bool audio_is_initialized = false; #endif -/* Buffer control thread. */ -static struct event_queue audio_queue; -static long audio_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)]; -static const char audio_thread_name[] = "audio"; - -/* Codec thread. */ -static struct event_queue codec_queue; -static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] -IBSS_ATTR; -static const char codec_thread_name[] = "codec"; - -/* Voice codec thread. */ -static struct event_queue voice_codec_queue; -static long voice_codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] -IBSS_ATTR; -static const char voice_codec_thread_name[] = "voice codec"; -struct voice_info { - void (*callback)(unsigned char **start, int *size); - int size; - char *buf; -}; static struct mutex mutex_codecthread; @@ -262,941 +241,746 @@ void (*track_changed_callback)(struct mp3entry *id3); void (*track_buffer_callback)(struct mp3entry *id3, bool last_track); void (*track_unbuffer_callback)(struct mp3entry *id3, bool last_track); -static void playback_init(void); - /* Configuration */ static size_t conf_watermark; static size_t conf_filechunk; static size_t conf_preseek; static size_t buffer_margin; - static bool v1first = false; -static void mp3_set_elapsed(struct mp3entry* id3); -static int mp3_get_file_pos(void); +/* Multiple threads */ +static const char * get_codec_filename(int enc_spec); -static void audio_clear_track_entries( - bool clear_buffered, bool clear_unbuffered, bool may_yield); -static bool audio_initialize_buffer_fill(bool clear_tracks); -static void audio_fill_file_buffer( - bool start_play, bool rebuffer, size_t offset); +/* Audio thread */ +static struct event_queue audio_queue; +static long audio_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)]; +static const char audio_thread_name[] = "audio"; -static void swap_codec(void) -{ - int my_codec = current_codec; +static void audio_thread(void); +static void set_filebuf_watermark(int seconds); +static void audio_initiate_track_change(long direction); +static bool audio_have_tracks(void); +static void audio_reset_buffer(void); - logf("swapping out codec:%d", my_codec); +/* Codec thread */ +static struct event_queue codec_queue; +static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] +IBSS_ATTR; +static const char codec_thread_name[] = "codec"; - /* Save our current IRAM and DRAM */ -#ifndef SIMULATOR - memcpy(iram_buf[my_codec], (unsigned char *)CODEC_IRAM_ORIGIN, - CODEC_IRAM_SIZE); +/* Voice thread */ +static struct event_queue voice_queue; +static long voice_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] +IBSS_ATTR; +static const char voice_thread_name[] = "voice codec"; +struct voice_info { + void (*callback)(unsigned char **start, int *size); + int size; + char *buf; +}; + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ +static void voice_boost_cpu(bool state); +#else +#define voice_boost_cpu(state) do { } while(0) #endif - memcpy(dram_buf[my_codec], codecbuf, CODEC_SIZE); - do { - /* Release my semaphore and force a task switch. */ - mutex_unlock(&mutex_codecthread); - yield(); - mutex_lock(&mutex_codecthread); - /* Loop until the other codec has locked and run */ - } while (my_codec == current_codec); - current_codec = my_codec; +static void voice_thread(void); - /* Reload our IRAM and DRAM */ -#ifndef SIMULATOR - memcpy((unsigned char *)CODEC_IRAM_ORIGIN, iram_buf[my_codec], - CODEC_IRAM_SIZE); -#endif - invalidate_icache(); - memcpy(codecbuf, dram_buf[my_codec], CODEC_SIZE); +/* --- External interfaces --- */ - logf("resuming codec:%d", my_codec); +void mp3_play_data(const unsigned char* start, int size, + void (*get_more)(unsigned char** start, int* size)) +{ + static struct voice_info voice_clip; + voice_clip.callback = get_more; + voice_clip.buf = (char *)start; + voice_clip.size = size; + logf("mp3 > voice Q_VOICE_STOP"); + queue_post(&voice_queue, Q_VOICE_STOP, 0); + logf("mp3 > voice Q_VOICE_PLAY"); + queue_post(&voice_queue, Q_VOICE_PLAY, &voice_clip); + voice_is_playing = true; + voice_boost_cpu(true); } -#ifdef HAVE_ADJUSTABLE_CPU_FREQ -static void voice_boost_cpu(bool state) +void mp3_play_stop(void) { - static bool voice_cpu_boosted = false; + logf("mp3 > voice Q_VOICE_STOP"); + queue_post(&voice_queue, Q_VOICE_STOP, 0); +} - if (state != voice_cpu_boosted) - { - cpu_boost(state); - voice_cpu_boosted = state; - } +bool mp3_pause_done(void) +{ + return pcm_is_paused(); } -#else -#define voice_boost_cpu(state) do { } while(0) -#endif -static bool voice_pcmbuf_insert_split_callback( - const void *ch1, const void *ch2, size_t length) +void mpeg_id3_options(bool _v1first) { - const char* src[2]; - char *dest; - long input_size; - size_t output_size; + v1first = _v1first; +} - src[0] = ch1; - src[1] = ch2; +void audio_load_encoder(int enc_id) +{ +#if defined(HAVE_RECORDING) && !defined(SIMULATOR) + const char *enc_fn = get_codec_filename(enc_id | CODEC_TYPE_ENCODER); + if (!enc_fn) + return; - if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) - length *= 2; /* Length is per channel */ + audio_remove_encoder(); - while (length) - { - long est_output_size = dsp_output_size(length); - - while ((dest = pcmbuf_request_voice_buffer(est_output_size, - &output_size, playing)) == NULL) - { - if (playing) - swap_codec(); - else - yield(); - } - - /* Get the real input_size for output_size bytes, guarding - * against resampling buffer overflows. */ - input_size = dsp_input_size(output_size); + LOGFQUEUE("audio > codec Q_ENCODER_LOAD_DISK"); + queue_post(&codec_queue, Q_ENCODER_LOAD_DISK, (void *)enc_fn); - if (input_size <= 0) - { - DEBUGF("Error: dsp_input_size(%ld=dsp_output_size(%ld))=%ld<=0\n", - output_size, length, input_size); - /* If this happens, there are samples of codec data that don't - * become a number of pcm samples, and something is broken */ - return false; - } + while (!ci.enc_codec_loaded) + yield(); +#endif + return; + (void)enc_id; +} /* audio_load_encoder */ - /* Input size has grown, no error, just don't write more than length */ - if ((size_t)input_size > length) - input_size = length; +void audio_remove_encoder(void) +{ +#if defined(HAVE_RECORDING) && !defined(SIMULATOR) + /* force encoder codec unload (if previously loaded) */ + if (!ci.enc_codec_loaded) + return; - output_size = dsp_process(dest, src, input_size); + ci.stop_codec = true; + while (ci.enc_codec_loaded) + yield(); +#endif +} /* audio_remove_encoder */ - if (playing) - { - pcmbuf_mix_voice(output_size); - if (pcmbuf_usage() < 10 || pcmbuf_mix_free() < 30) - swap_codec(); - } - else - pcmbuf_write_complete(output_size); +struct mp3entry* audio_current_track(void) +{ + const char *filename; + const char *p; + static struct mp3entry temp_id3; + int cur_idx; + + cur_idx = track_ridx + ci.new_track; + cur_idx &= MAX_TRACK_MASK; - length -= input_size; - } + if (tracks[cur_idx].taginfo_ready) + return &tracks[cur_idx].id3; - return true; -} /* voice_pcmbuf_insert_split_callback */ + memset(&temp_id3, 0, sizeof(struct mp3entry)); + + filename = playlist_peek(ci.new_track); + if (!filename) + filename = "No file!"; -static bool codec_pcmbuf_insert_split_callback( - const void *ch1, const void *ch2, size_t length) -{ - const char* src[2]; - char *dest; - long input_size; - size_t output_size; +#ifdef HAVE_TC_RAMCACHE + if (tagcache_fill_tags(&temp_id3, filename)) + return &temp_id3; +#endif - src[0] = ch1; - src[1] = ch2; + p = strrchr(filename, '/'); + if (!p) + p = filename; + else + p++; - if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) - length *= 2; /* Length is per channel */ + strncpy(temp_id3.path, p, sizeof(temp_id3.path)-1); + temp_id3.title = &temp_id3.path[0]; - while (length) - { - long est_output_size = dsp_output_size(length); - /* Prevent audio from a previous track from playing */ - if (ci.new_track || ci.stop_codec) - return true; + return &temp_id3; +} - while ((dest = pcmbuf_request_buffer(est_output_size, - &output_size)) == NULL) - { - sleep(1); - if (ci.seek_time || ci.new_track || ci.stop_codec) - return true; - } +struct mp3entry* audio_next_track(void) +{ + int next_idx = track_ridx; - /* Get the real input_size for output_size bytes, guarding - * against resampling buffer overflows. */ - input_size = dsp_input_size(output_size); + if (!audio_have_tracks()) + return NULL; - if (input_size <= 0) - { - DEBUGF("Error: dsp_input_size(%ld=dsp_output_size(%ld))=%ld<=0\n", - output_size, length, input_size); - /* If this happens, there are samples of codec data that don't - * become a number of pcm samples, and something is broken */ - return false; - } + next_idx++; + next_idx &= MAX_TRACK_MASK; - /* Input size has grown, no error, just don't write more than length */ - if ((size_t)input_size > length) - input_size = length; + if (!tracks[next_idx].taginfo_ready) + return NULL; - output_size = dsp_process(dest, src, input_size); + return &tracks[next_idx].id3; +} - pcmbuf_write_complete(output_size); +bool audio_has_changed_track(void) +{ + if (track_changed) + { + track_changed = false; + return true; + } - if (voice_is_playing && pcm_is_playing() && - pcmbuf_usage() > 30 && pcmbuf_mix_free() > 80) - { - swap_codec(); - } - - length -= input_size; - } - - return true; -} /* codec_pcmbuf_insert_split_callback */ - -static bool voice_pcmbuf_insert_callback(const char *buf, size_t length) -{ - /* TODO: The audiobuffer API should probably be updated, and be based on - * pcmbuf_insert_split(). */ - long real_length = length; - - if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) - length /= 2; /* Length is per channel */ - - /* Second channel is only used for non-interleaved stereo. */ - return voice_pcmbuf_insert_split_callback(buf, buf + (real_length / 2), - length); + return false; } -static bool codec_pcmbuf_insert_callback(const char *buf, size_t length) +void audio_play(long offset) { - /* TODO: The audiobuffer API should probably be updated, and be based on - * pcmbuf_insert_split(). */ - long real_length = length; - - if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) - length /= 2; /* Length is per channel */ + logf("audio_play"); + if (playing && offset <= 0) + { + LOGFQUEUE("audio > audio Q_AUDIO_NEW_PLAYLIST"); + queue_post(&audio_queue, Q_AUDIO_NEW_PLAYLIST, 0); + } + else + { + if (playing) + audio_stop(); - /* Second channel is only used for non-interleaved stereo. */ - return codec_pcmbuf_insert_split_callback(buf, buf + (real_length / 2), - length); + playing = true; + LOGFQUEUE("audio > audio Q_AUDIO_PLAY"); + queue_post(&audio_queue, Q_AUDIO_PLAY, (void *)offset); + } } -static void* get_voice_memory_callback(size_t *size) +void audio_stop(void) { - *size = 0; - return NULL; + LOGFQUEUE("audio > audio Q_AUDIO_STOP"); + queue_post(&audio_queue, Q_AUDIO_STOP, 0); + while (playing || audio_codec_loaded) + yield(); } -static void* get_codec_memory_callback(size_t *size) +void audio_pause(void) { - *size = MALLOC_BUFSIZE; - return &audiobuf[talk_get_bufsize()]; + LOGFQUEUE("audio > audio Q_AUDIO_PAUSE"); + queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)true); } -static void pcmbuf_position_callback(size_t size) ICODE_ATTR; -static void pcmbuf_position_callback(size_t size) +void audio_resume(void) { - unsigned int time = size * 1000 / 4 / NATIVE_FREQUENCY + - prev_ti->id3.elapsed; - - if (time >= prev_ti->id3.length) - { - pcmbuf_set_position_callback(NULL); - prev_ti->id3.elapsed = prev_ti->id3.length; - } - else - prev_ti->id3.elapsed = time; + LOGFQUEUE("audio > audio Q_AUDIO_PAUSE resume"); + queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)false); } -static void voice_set_elapsed_callback(unsigned int value) +void audio_next(void) { - (void)value; + if (global_settings.beep) + pcmbuf_beep(5000, 100, 2500*global_settings.beep); + + /* Should be safe to do outside of thread, that way we get + * the instant wps response at least. */ + audio_initiate_track_change(1); + // queue_post(&audio_queue, Q_AUDIO_SKIP, (void *)1); } -static void codec_set_elapsed_callback(unsigned int value) +void audio_prev(void) { - unsigned int latency; - if (ci.seek_time) - return; - -#ifdef AB_REPEAT_ENABLE - ab_position_report(value); -#endif + if (global_settings.beep) + pcmbuf_beep(5000, 100, 2500*global_settings.beep); - latency = pcmbuf_get_latency(); - if (value < latency) - cur_ti->id3.elapsed = 0; - else if (value - latency > cur_ti->id3.elapsed || - value - latency < cur_ti->id3.elapsed - 2) - { - cur_ti->id3.elapsed = value - latency; - } + audio_initiate_track_change(-1); + // queue_post(&audio_queue, Q_AUDIO_SKIP, (void *)-1); } -static void voice_set_offset_callback(size_t value) +void audio_next_dir(void) { - (void)value; + LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP 1"); + queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)1); } -static void codec_set_offset_callback(size_t value) +void audio_prev_dir(void) { - unsigned int latency; - - if (ci.seek_time) - return; - - latency = pcmbuf_get_latency() * cur_ti->id3.bitrate / 8; - if (value < latency) - cur_ti->id3.offset = 0; - else - cur_ti->id3.offset = value - latency; + LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP -1"); + queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)-1); } -static bool filebuf_is_lowdata(void) +void audio_pre_ff_rewind(void) { - return filebufused < AUDIO_FILEBUF_CRITICAL; + LOGFQUEUE("audio > audio Q_AUDIO_PRE_FF_REWIND"); + queue_post(&audio_queue, Q_AUDIO_PRE_FF_REWIND, 0); } -static bool have_tracks(void) +void audio_ff_rewind(long newpos) { - return track_ridx != track_widx || cur_ti->filesize; + LOGFQUEUE("audio > audio Q_AUDIO_FF_REWIND"); + queue_post(&audio_queue, Q_AUDIO_FF_REWIND, (int *)newpos); } -static bool have_free_tracks(void) +void audio_flush_and_reload_tracks(void) { - if (track_widx < track_ridx) - return track_widx + 1 < track_ridx; - else if (track_ridx == 0) - return track_widx < MAX_TRACK - 1; - - return true; + LOGFQUEUE("audio > audio Q_AUDIO_FLUSH"); + queue_post(&audio_queue, Q_AUDIO_FLUSH, 0); } - -int audio_track_count(void) + +void audio_error_clear(void) { - if (have_tracks()) - { - int relative_track_widx = track_widx; - - if (track_ridx > track_widx) - relative_track_widx += MAX_TRACK; - - return relative_track_widx - track_ridx + 1; - } - - return 0; } -static void codec_advance_buffer_counters(size_t amount) +int audio_status(void) { - buf_ridx += amount; - - if (buf_ridx >= filebuflen) - buf_ridx -= filebuflen; - - ci.curpos += amount; - cur_ti->available -= amount; - filebufused -= amount; + int ret = 0; - /* Start buffer filling as necessary. */ - if (!pcmbuf_is_lowdata() && !filling) - { - if (conf_watermark && filebufused <= conf_watermark && playing) - { - LOGFQUEUE("codec > audio Q_AUDIO_FILL_BUFFER"); - queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); - } - } + if (playing) + ret |= AUDIO_STATUS_PLAY; + + if (paused) + ret |= AUDIO_STATUS_PAUSE; + +#ifdef HAVE_RECORDING + /* Do this here for constitency with mpeg.c version */ + ret |= pcm_rec_status(); +#endif + + return ret; } -static size_t voice_filebuf_callback(void *ptr, size_t size) +bool audio_query_poweroff(void) { - (void)ptr; - (void)size; + return !(playing && paused); +} +int audio_get_file_pos(void) +{ return 0; } -/* copy up-to size bytes into ptr and return the actual size copied */ -static size_t codec_filebuf_callback(void *ptr, size_t size) +void audio_set_buffer_margin(int setting) { - char *buf = (char *)ptr; - size_t copy_n; - size_t part_n; + static const int lookup[] = {5, 15, 30, 60, 120, 180, 300, 600}; + buffer_margin = lookup[setting]; + logf("buffer margin: %ds", buffer_margin); + set_filebuf_watermark(buffer_margin); +} - if (ci.stop_codec || !playing) - return 0; +/* Set crossfade & PCM buffer length. */ +void audio_set_crossfade(int enable) +{ + size_t size; + bool was_playing = (playing && audio_is_initialized); + size_t offset = 0; +#if MEM > 1 + int seconds = 1; +#endif - /* The ammount to copy is the lesser of the requested amount and the - * amount left of the current track (both on disk and already loaded) */ - copy_n = MIN(size, cur_ti->available + cur_ti->filerem); + if (!filebuf) + return; /* Audio buffers not yet set up */ - /* Nothing requested OR nothing left */ - if (copy_n == 0) - return 0; +#if MEM > 1 + if (enable) + seconds = global_settings.crossfade_fade_out_delay + + global_settings.crossfade_fade_out_duration; - /* Let the disk buffer catch fill until enough data is available */ - while (copy_n > cur_ti->available) - { - if (!filling) - { - LOGFQUEUE("codec > audio Q_AUDIO_FILL_BUFFER"); - queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); - } - - sleep(1); - if (ci.stop_codec || ci.new_track) - return 0; - } + /* Buffer has to be at least 2s long. */ + seconds += 2; + logf("buf len: %d", seconds); + size = seconds * (NATIVE_FREQUENCY*4); +#else + enable = 0; + size = NATIVE_FREQUENCY*2; +#endif + if (pcmbuf_get_bufsize() == size) + return ; - /* Copy as much as possible without wrapping */ - part_n = MIN(copy_n, filebuflen - buf_ridx); - memcpy(buf, &filebuf[buf_ridx], part_n); - /* Copy the rest in the case of a wrap */ - if (part_n < copy_n) { - memcpy(&buf[part_n], &filebuf[0], copy_n - part_n); + if (was_playing) + { + /* Store the track resume position */ + offset = cur_ti->id3.offset; + /* Playback has to be stopped before changing the buffer size. */ + LOGFQUEUE("audio > audio Q_AUDIO_STOP"); + queue_post(&audio_queue, Q_AUDIO_STOP, 0); + while (audio_codec_loaded) + yield(); + gui_syncsplash(0, true, (char *)str(LANG_RESTARTING_PLAYBACK)); } - /* Update read and other position pointers */ - codec_advance_buffer_counters(copy_n); + /* Re-initialize audio system. */ + pcmbuf_init(size); + pcmbuf_crossfade_enable(enable); + audio_reset_buffer(); + logf("abuf:%dB", pcmbuf_get_bufsize()); + logf("fbuf:%dB", filebuflen); - /* Return the actual amount of data copied to the buffer */ - return copy_n; -} /* codec_filebuf_callback */ + voice_init(); -static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize) -{ - struct event ev; + /* Restart playback. */ + if (was_playing) { + playing = true; + LOGFQUEUE("audio > audio Q_AUDIO_PLAY"); + queue_post(&audio_queue, Q_AUDIO_PLAY, (void *)offset); - if (ci_voice.new_track) - { - *realsize = 0; - return NULL; + /* Wait for the playback to start again (and display the splash + screen during that period. */ + while (playing && !audio_codec_loaded) + yield(); } +} - while (1) - { - if (voice_is_playing) - { - queue_wait_w_tmo(&voice_codec_queue, &ev, 0); - } - else if (playing) - { - queue_wait_w_tmo(&voice_codec_queue, &ev, 0); - if (ev.id == SYS_TIMEOUT) - ev.id = Q_AUDIO_PLAY; - } - else - queue_wait(&voice_codec_queue, &ev); - - switch (ev.id) { - case Q_AUDIO_PLAY: - LOGFQUEUE("voice < Q_AUDIO_PLAY"); - if (playing) - swap_codec(); - break; - -#if defined(HAVE_RECORDING) && !defined(SIMULATOR) - case Q_ENCODER_RECORD: - LOGFQUEUE("voice < Q_ENCODER_RECORD"); - swap_codec(); - break; -#endif +void audio_preinit(void) +{ + logf("playback system pre-init"); - case Q_VOICE_STOP: - LOGFQUEUE("voice < Q_VOICE_STOP"); - if (voice_is_playing) - { - /* Clear the current buffer */ - voice_is_playing = false; - voice_getmore = NULL; - voice_remaining = 0; - voicebuf = NULL; - voice_boost_cpu(false); - ci_voice.new_track = 1; - /* Force the codec to think it's changing tracks */ - *realsize = 0; - return NULL; - } - else - break; + filebufused = 0; + filling = false; + current_codec = CODEC_IDX_AUDIO; + playing = false; + paused = false; + audio_codec_loaded = false; + voice_is_playing = false; + track_changed = false; + current_fd = -1; + track_buffer_callback = NULL; + track_unbuffer_callback = NULL; + track_changed_callback = NULL; + /* Just to prevent cur_ti never be anything random. */ + cur_ti = &tracks[0]; - case SYS_USB_CONNECTED: - LOGFQUEUE("voice < SYS_USB_CONNECTED"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); - if (audio_codec_loaded) - swap_codec(); - usb_wait_for_disconnect(&voice_codec_queue); - break; + mutex_init(&mutex_codecthread); - case Q_VOICE_PLAY: - LOGFQUEUE("voice < Q_VOICE_PLAY"); - { - struct voice_info *voice_data; - voice_is_playing = true; - voice_boost_cpu(true); - voice_data = ev.data; - voice_remaining = voice_data->size; - voicebuf = voice_data->buf; - voice_getmore = voice_data->callback; - } + queue_init(&audio_queue); + queue_init(&codec_queue); + /* clear, not init to create a private queue */ + queue_clear(&codec_callback_queue); - case SYS_TIMEOUT: - LOGFQUEUE("voice < SYS_TIMEOUT"); - goto voice_play_clip; + create_thread(audio_thread, audio_stack, sizeof(audio_stack), + audio_thread_name); +} - default: - LOGFQUEUE("voice < default"); - } - } +void audio_init(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_POSTINIT"); + queue_post(&audio_queue, Q_AUDIO_POSTINIT, 0); +} -voice_play_clip: +void voice_init(void) +{ + if (!filebuf) + return; /* Audio buffers not yet set up */ - if (voice_remaining == 0 || voicebuf == NULL) + if (voice_thread_num >= 0) { - if (voice_getmore) - voice_getmore((unsigned char **)&voicebuf, (int *)&voice_remaining); - - /* If this clip is done */ - if (!voice_remaining) - { - LOGFQUEUE("voice > voice Q_VOICE_STOP"); - queue_post(&voice_codec_queue, Q_VOICE_STOP, 0); - /* Force pcm playback. */ - if (!pcm_is_playing()) - pcmbuf_play_start(); - } + logf("Terminating voice codec"); + remove_thread(voice_thread_num); + if (current_codec == CODEC_IDX_VOICE) + mutex_unlock(&mutex_codecthread); + queue_delete(&voice_queue); + voice_thread_num = -1; + voice_codec_loaded = false; } - - *realsize = MIN(voice_remaining, reqsize); - if (*realsize == 0) - return NULL; + if (!talk_voice_required()) + return; - return voicebuf; -} /* voice_request_buffer_callback */ + logf("Starting voice codec"); + queue_init(&voice_queue); + voice_thread_num = create_thread(voice_thread, voice_stack, + sizeof(voice_stack), voice_thread_name); + + while (!voice_codec_loaded) + yield(); +} /* voice_init */ -static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) +void voice_stop(void) { - size_t short_n, copy_n, buf_rem; - - if (!playing) - { - *realsize = 0; - return NULL; - } + /* Messages should not be posted to voice codec queue unless it is the + current codec or deadlocks happen. This will be addressed globally soon. + -- jhMikeS */ + if (current_codec != CODEC_IDX_VOICE) + return; - copy_n = MIN(reqsize, cur_ti->available + cur_ti->filerem); - if (copy_n == 0) - { - *realsize = 0; - return NULL; - } + mp3_play_stop(); + while (voice_is_playing && !queue_empty(&voice_queue)) + yield(); +} /* voice_stop */ - while (copy_n > cur_ti->available) - { - if (!filling) - { - LOGFQUEUE("codec > audio Q_AUDIO_FILL_BUFFER"); - queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); - } - - sleep(1); - if (ci.stop_codec || ci.new_track) - { - *realsize = 0; - return NULL; - } - } - /* How much is left at the end of the file buffer before wrap? */ - buf_rem = filebuflen - buf_ridx; - - /* If we can't satisfy the request without wrapping */ - if (buf_rem < copy_n) - { - /* How short are we? */ - short_n = copy_n - buf_rem; - - /* If we can fudge it with the guardbuf */ - if (short_n < GUARD_BUFSIZE) - memcpy(&filebuf[filebuflen], &filebuf[0], short_n); - else - copy_n = buf_rem; - } - *realsize = copy_n; - - return (char *)&filebuf[buf_ridx]; -} /* codec_request_buffer_callback */ +/* --- Routines called from multiple threads --- */ -static int get_codec_base_type(int type) +static void swap_codec(void) { - switch (type) { - case AFMT_MPA_L1: - case AFMT_MPA_L2: - case AFMT_MPA_L3: - return AFMT_MPA_L3; - } + int my_codec = current_codec; - return type; -} + logf("swapping out codec:%d", my_codec); -/* Count the data BETWEEN the selected tracks */ -static size_t buffer_count_tracks(int from_track, int to_track) -{ - size_t amount = 0; - bool need_wrap = to_track < from_track; + /* Save our current IRAM and DRAM */ +#ifndef SIMULATOR + memcpy(iram_buf[my_codec], (unsigned char *)CODEC_IRAM_ORIGIN, + CODEC_IRAM_SIZE); +#endif + memcpy(dram_buf[my_codec], codecbuf, CODEC_SIZE); - while (1) - { - if (++from_track >= MAX_TRACK) - { - from_track -= MAX_TRACK; - need_wrap = false; - } - - if (from_track >= to_track && !need_wrap) - break; - - amount += tracks[from_track].codecsize + tracks[from_track].filesize; - } - return amount; + do { + /* Release my semaphore and force a task switch. */ + mutex_unlock(&mutex_codecthread); + yield(); + mutex_lock(&mutex_codecthread); + /* Loop until the other codec has locked and run */ + } while (my_codec == current_codec); + current_codec = my_codec; + + /* Reload our IRAM and DRAM */ +#ifndef SIMULATOR + memcpy((unsigned char *)CODEC_IRAM_ORIGIN, iram_buf[my_codec], + CODEC_IRAM_SIZE); +#endif + invalidate_icache(); + memcpy(codecbuf, dram_buf[my_codec], CODEC_SIZE); + + logf("resuming codec:%d", my_codec); } -static bool buffer_wind_forward(int new_track_ridx, int old_track_ridx) +static void set_filebuf_watermark(int seconds) { - size_t amount; - - /* Start with the remainder of the previously playing track */ - amount = tracks[old_track_ridx].filesize - ci.curpos; - /* Then collect all data from tracks in between them */ - amount += buffer_count_tracks(old_track_ridx, new_track_ridx); - - if (amount > filebufused) - return false; + size_t bytes; - logf("bwf:%ldB",amount); + if (current_codec == CODEC_IDX_VOICE) + return; - /* Wind the buffer to the beginning of the target track or its codec */ - buf_ridx += amount; - filebufused -= amount; - - /* Check and handle buffer wrapping */ - if (buf_ridx >= filebuflen) - buf_ridx -= filebuflen; + if (!filebuf) + return; /* Audio buffers not yet set up */ - return true; + bytes = MAX(cur_ti->id3.bitrate * seconds * (1000/8), conf_watermark); + bytes = MIN(bytes, filebuflen / 2); + conf_watermark = bytes; } -static bool buffer_wind_backward(int new_track_ridx, int old_track_ridx) +static const char * get_codec_filename(int enc_spec) { - /* Available buffer data */ - size_t buf_back; - /* Start with the previously playing track's data and our data */ - size_t amount; - - buf_back = buf_ridx; - amount = ci.curpos; - if (buf_ridx < buf_widx) - buf_back += filebuflen; - buf_back -= buf_widx; - - /* If we're not just resetting the current track */ - if (new_track_ridx != old_track_ridx) - { - /* Need to wind to before the old track's codec and our filesize */ - amount += tracks[old_track_ridx].codecsize; - amount += tracks[new_track_ridx].filesize; - - /* Rewind the old track to its beginning */ - tracks[old_track_ridx].available = - tracks[old_track_ridx].filesize - tracks[old_track_ridx].filerem; - } - - /* If the codec was ever buffered */ - if (tracks[new_track_ridx].codecsize) - { - /* Add the codec to the needed size */ - amount += tracks[new_track_ridx].codecsize; - tracks[new_track_ridx].has_codec = true; - } + const char *fname; + int type = enc_spec & CODEC_TYPE_MASK; + int afmt = enc_spec & CODEC_AFMT_MASK; - /* Then collect all data from tracks between new and old */ - amount += buffer_count_tracks(new_track_ridx, old_track_ridx); + if ((unsigned)afmt >= AFMT_NUM_CODECS) + type = AFMT_UNKNOWN | (type & CODEC_TYPE_MASK); - /* Do we have space to make this skip? */ - if (amount > buf_back) - return false; + fname = (type == CODEC_TYPE_DECODER) ? + audio_formats[afmt].codec_fn : audio_formats[afmt].codec_enc_fn; - logf("bwb:%ldB",amount); + logf("%s: %d - %s", + (type == CODEC_TYPE_ENCODER) ? "Encoder" : "Decoder", + afmt, fname ? fname : ""); - /* Check and handle buffer wrapping */ - if (amount > buf_ridx) - buf_ridx += filebuflen; - /* Rewind the buffer to the beginning of the target track or its codec */ - buf_ridx -= amount; - filebufused += amount; + return fname; +} /* get_codec_filename */ - /* Reset to the beginning of the new track */ - tracks[new_track_ridx].available = tracks[new_track_ridx].filesize; - return true; -} +/* --- Voice thread --- */ -static void audio_update_trackinfo(void) +#ifdef HAVE_ADJUSTABLE_CPU_FREQ +static void voice_boost_cpu(bool state) { - ci.filesize = cur_ti->filesize; - cur_ti->id3.elapsed = 0; - cur_ti->id3.offset = 0; - ci.id3 = &cur_ti->id3; - ci.curpos = 0; - ci.taginfo_ready = &cur_ti->taginfo_ready; -} + static bool voice_cpu_boosted = false; -static void audio_rebuffer(void) -{ - logf("Forcing rebuffer"); - - /* Notify the codec that this will take a while */ - /* Currently this can cause some problems (logf in reverse order): - * Codec load error:-1 - * Codec load disk - * Codec: Unsupported - * Codec finished - * New codec:0/3 - * Clearing tracks:7/7, 1 - * Forcing rebuffer - * Check new track buffer - * Request new track - * Clearing tracks:5/5, 0 - * Starting buffer fill - * Clearing tracks:5/5, 1 - * Re-buffering song w/seek - */ - //if (!filling) - // queue_post(&codec_callback_queue, Q_CODEC_REQUEST_PENDING, 0); - - /* Stop in progress fill, and clear open file descriptor */ - if (current_fd >= 0) + if (state != voice_cpu_boosted) { - close(current_fd); - current_fd = -1; + cpu_boost(state); + voice_cpu_boosted = state; } - filling = false; - - /* Reset buffer and track pointers */ - tracks[track_ridx].buf_idx = buf_ridx = buf_widx = 0; - track_widx = track_ridx; - cur_ti = &tracks[track_ridx]; - audio_clear_track_entries(true, true, false); - filebufused = 0; - cur_ti->available = 0; - - /* Fill the buffer */ - last_peek_offset = -1; - cur_ti->filesize = 0; - cur_ti->start_pos = 0; - ci.curpos = 0; - - if (!cur_ti->taginfo_ready) - memset(&cur_ti->id3, 0, sizeof(struct mp3entry)); - - audio_fill_file_buffer(false, true, 0); } +#endif -static void audio_check_new_track(void) +static bool voice_pcmbuf_insert_split_callback( + const void *ch1, const void *ch2, size_t length) { - int track_count = audio_track_count(); - int old_track_ridx = track_ridx; - bool forward; + const char* src[2]; + char *dest; + long input_size; + size_t output_size; - if (dir_skip) + src[0] = ch1; + src[1] = ch2; + + if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) + length *= 2; /* Length is per channel */ + + while (length) { - dir_skip = false; - if (playlist_next_dir(ci.new_track)) + long est_output_size = dsp_output_size(length); + + while ((dest = pcmbuf_request_voice_buffer(est_output_size, + &output_size, playing)) == NULL) { - ci.new_track = 0; - cur_ti->taginfo_ready = false; - audio_rebuffer(); - goto skip_done; + if (playing) + swap_codec(); + else + yield(); } - else + + /* Get the real input_size for output_size bytes, guarding + * against resampling buffer overflows. */ + input_size = dsp_input_size(output_size); + + if (input_size <= 0) { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; + DEBUGF("Error: dsp_input_size(%ld=dsp_output_size(%ld))=%ld<=0\n", + output_size, length, input_size); + /* If this happens, there are samples of codec data that don't + * become a number of pcm samples, and something is broken */ + return false; } - } - if (new_playlist) - ci.new_track = 0; + /* Input size has grown, no error, just don't write more than length */ + if ((size_t)input_size > length) + input_size = length; - /* If the playlist isn't that big */ - if (!playlist_check(ci.new_track)) - { - if (ci.new_track >= 0) + output_size = dsp_process(dest, src, input_size); + + if (playing) { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; + pcmbuf_mix_voice(output_size); + if (pcmbuf_usage() < 10 || pcmbuf_mix_free() < 30) + swap_codec(); } - /* Find the beginning backward if the user over-skips it */ - while (!playlist_check(++ci.new_track)) - if (ci.new_track >= 0) - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } + else + pcmbuf_write_complete(output_size); + + length -= input_size; } - /* Update the playlist */ - last_peek_offset -= ci.new_track; - if (playlist_next(ci.new_track) < 0) - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } + return true; +} /* voice_pcmbuf_insert_split_callback */ - if (new_playlist) - { - ci.new_track = 1; - new_playlist = false; - } +static bool voice_pcmbuf_insert_callback(const char *buf, size_t length) +{ + /* TODO: The audiobuffer API should probably be updated, and be based on + * pcmbuf_insert_split(). */ + long real_length = length; - track_ridx += ci.new_track; - track_ridx &= MAX_TRACK_MASK; + if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) + length /= 2; /* Length is per channel */ - /* Save the old track */ - prev_ti = cur_ti; - /* Move to the new track */ - cur_ti = &tracks[track_ridx]; + /* Second channel is only used for non-interleaved stereo. */ + return voice_pcmbuf_insert_split_callback(buf, buf + (real_length / 2), + length); +} - if (automatic_skip) - playlist_end = false; +static void* voice_get_memory_callback(size_t *size) +{ + *size = 0; + return NULL; +} - track_changed = !automatic_skip; +static void voice_set_elapsed_callback(unsigned int value) +{ + (void)value; +} - /* If it is not safe to even skip this many track entries */ - if (ci.new_track >= track_count || ci.new_track <= track_count - MAX_TRACK) - { - ci.new_track = 0; - cur_ti->taginfo_ready = false; - audio_rebuffer(); - goto skip_done; - } +static void voice_set_offset_callback(size_t value) +{ + (void)value; +} - forward = ci.new_track > 0; - ci.new_track = 0; +static size_t voice_filebuf_callback(void *ptr, size_t size) +{ + (void)ptr; + (void)size; - /* If the target track is clearly not in memory */ - if (cur_ti->filesize == 0 || !cur_ti->taginfo_ready) - { - audio_rebuffer(); - goto skip_done; - } + return 0; +} - /* The track may be in memory, see if it really is */ - if (forward) +static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize) +{ + struct event ev; + + if (ci_voice.new_track) { - if (!buffer_wind_forward(track_ridx, old_track_ridx)) - audio_rebuffer(); + *realsize = 0; + return NULL; } - else + + while (1) { - int cur_idx = track_ridx; - bool taginfo_ready = true; - bool wrap = track_ridx > old_track_ridx; - - while (1) + if (voice_is_playing) { - cur_idx++; - cur_idx &= MAX_TRACK_MASK; - if (!(wrap || cur_idx < old_track_ridx)) - break; - - /* If we hit a track in between without valid tag info, bail */ - if (!tracks[cur_idx].taginfo_ready) - { - taginfo_ready = false; - break; - } - - tracks[cur_idx].available = tracks[cur_idx].filesize; - if (tracks[cur_idx].codecsize) - tracks[cur_idx].has_codec = true; + queue_wait_w_tmo(&voice_queue, &ev, 0); } - if (taginfo_ready) + else if (playing) { - if (!buffer_wind_backward(track_ridx, old_track_ridx)) - audio_rebuffer(); + queue_wait_w_tmo(&voice_queue, &ev, 0); + if (ev.id == SYS_TIMEOUT) + ev.id = Q_AUDIO_PLAY; } else - { - cur_ti->taginfo_ready = false; - audio_rebuffer(); - } - } + queue_wait(&voice_queue, &ev); -skip_done: - audio_update_trackinfo(); - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); -} + switch (ev.id) { + case Q_AUDIO_PLAY: + LOGFQUEUE("voice < Q_AUDIO_PLAY"); + if (playing) + swap_codec(); + break; -static void audio_rebuffer_and_seek(size_t newpos) -{ - int fd; - char *trackname; +#if defined(HAVE_RECORDING) && !defined(SIMULATOR) + case Q_ENCODER_RECORD: + LOGFQUEUE("voice < Q_ENCODER_RECORD"); + swap_codec(); + break; +#endif - trackname = playlist_peek(0); - /* (Re-)open current track's file handle. */ + case Q_VOICE_STOP: + LOGFQUEUE("voice < Q_VOICE_STOP"); + if (voice_is_playing) + { + /* Clear the current buffer */ + voice_is_playing = false; + voice_getmore = NULL; + voice_remaining = 0; + voicebuf = NULL; + voice_boost_cpu(false); + ci_voice.new_track = 1; + /* Force the codec to think it's changing tracks */ + *realsize = 0; + return NULL; + } + else + break; - fd = open(trackname, O_RDONLY); - if (fd < 0) - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } - - if (current_fd >= 0) - close(current_fd); - current_fd = fd; + case SYS_USB_CONNECTED: + LOGFQUEUE("voice < SYS_USB_CONNECTED"); + usb_acknowledge(SYS_USB_CONNECTED_ACK); + if (audio_codec_loaded) + swap_codec(); + usb_wait_for_disconnect(&voice_queue); + break; - playlist_end = false; + case Q_VOICE_PLAY: + LOGFQUEUE("voice < Q_VOICE_PLAY"); + { + struct voice_info *voice_data; + voice_is_playing = true; + voice_boost_cpu(true); + voice_data = ev.data; + voice_remaining = voice_data->size; + voicebuf = voice_data->buf; + voice_getmore = voice_data->callback; + } - ci.curpos = newpos; + case SYS_TIMEOUT: + LOGFQUEUE("voice < SYS_TIMEOUT"); + goto voice_play_clip; - /* Clear codec buffer. */ - track_widx = track_ridx; - filebufused = 0; - tracks[track_widx].buf_idx = buf_widx = buf_ridx = 0; + default: + LOGFQUEUE("voice < default"); + } + } - last_peek_offset = 0; - filling = false; - audio_initialize_buffer_fill(true); - filling = true; +voice_play_clip: - if (newpos > conf_preseek) { - buf_ridx += conf_preseek; - cur_ti->start_pos = newpos - conf_preseek; - } - else + if (voice_remaining == 0 || voicebuf == NULL) { - buf_ridx += newpos; - cur_ti->start_pos = 0; - } + if (voice_getmore) + voice_getmore((unsigned char **)&voicebuf, (int *)&voice_remaining); - cur_ti->filerem = cur_ti->filesize - cur_ti->start_pos; - cur_ti->available = 0; + /* If this clip is done */ + if (!voice_remaining) + { + LOGFQUEUE("voice > voice Q_VOICE_STOP"); + queue_post(&voice_queue, Q_VOICE_STOP, 0); + /* Force pcm playback. */ + if (!pcm_is_playing()) + pcmbuf_play_start(); + } + } + + *realsize = MIN(voice_remaining, reqsize); - lseek(current_fd, cur_ti->start_pos, SEEK_SET); + if (*realsize == 0) + return NULL; - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); -} + return voicebuf; +} /* voice_request_buffer_callback */ static void voice_advance_buffer_callback(size_t amount) { @@ -1205,985 +989,581 @@ static void voice_advance_buffer_callback(size_t amount) voice_remaining -= amount; } -static void codec_advance_buffer_callback(size_t amount) +static void voice_advance_buffer_loc_callback(void *ptr) { - if (amount > cur_ti->available + cur_ti->filerem) - amount = cur_ti->available + cur_ti->filerem; + size_t amount = (size_t)ptr - (size_t)voicebuf; + + voice_advance_buffer_callback(amount); +} - while (amount > cur_ti->available && filling) - sleep(1); +static off_t voice_mp3_get_filepos_callback(int newtime) +{ + (void)newtime; + + return 0; +} - if (amount > cur_ti->available) - { - struct event ev; - - LOGFQUEUE("codec > audio Q_AUDIO_REBUFFER_SEEK"); - queue_post(&audio_queue, - Q_AUDIO_REBUFFER_SEEK, (void *)(ci.curpos + amount)); - - queue_wait(&codec_callback_queue, &ev); - switch (ev.id) - { - case Q_CODEC_REQUEST_FAILED: - LOGFQUEUE("codec < Q_CODEC_REQUEST_FAILED"); - ci.stop_codec = true; - return; - - case Q_CODEC_REQUEST_COMPLETE: - LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); - return; - - default: - LOGFQUEUE("codec < default"); - ci.stop_codec = true; - return; - } - } - - codec_advance_buffer_counters(amount); - - codec_set_offset_callback(ci.curpos); -} - -static void voice_advance_buffer_loc_callback(void *ptr) +static void voice_do_nothing(void) { - size_t amount = (size_t)ptr - (size_t)voicebuf; - - voice_advance_buffer_callback(amount); + return; } -static void codec_advance_buffer_loc_callback(void *ptr) +static bool voice_seek_buffer_callback(size_t newpos) { - size_t amount = (size_t)ptr - (size_t)&filebuf[buf_ridx]; + (void)newpos; - codec_advance_buffer_callback(amount); + return false; } -static off_t voice_mp3_get_filepos_callback(int newtime) +static bool voice_request_next_track_callback(void) { - (void)newtime; - - return 0; + ci_voice.new_track = 0; + return true; } -static off_t codec_mp3_get_filepos_callback(int newtime) +static void voice_thread(void) { - off_t newpos; - - cur_ti->id3.elapsed = newtime; - newpos = mp3_get_file_pos(); - - return newpos; -} + while (1) + { + logf("Loading voice codec"); + voice_codec_loaded = true; + mutex_lock(&mutex_codecthread); + current_codec = CODEC_IDX_VOICE; + dsp_configure(DSP_RESET, 0); + voice_remaining = 0; + voice_getmore = NULL; -static void voice_do_nothing(void) -{ - return; -} + codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice); -static void codec_seek_complete_callback(void) -{ - logf("seek_complete"); - if (pcm_is_paused()) - { - /* If this is not a seamless seek, clear the buffer */ - pcmbuf_play_stop(); - - /* If playback was not 'deliberately' paused, unpause now */ - if (!paused) - pcmbuf_pause(false); + logf("Voice codec finished"); + voice_codec_loaded = false; + mutex_unlock(&mutex_codecthread); } - ci.seek_time = 0; -} +} /* voice_thread */ -static bool voice_seek_buffer_callback(size_t newpos) -{ - (void)newpos; - - return false; -} -static bool codec_seek_buffer_callback(size_t newpos) +/* --- Codec thread --- */ + +static bool codec_pcmbuf_insert_split_callback( + const void *ch1, const void *ch2, size_t length) { - int difference; + const char* src[2]; + char *dest; + long input_size; + size_t output_size; - logf("codec_seek_buffer_callback"); + src[0] = ch1; + src[1] = ch2; - if (newpos >= cur_ti->filesize) - newpos = cur_ti->filesize - 1; + if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) + length *= 2; /* Length is per channel */ - difference = newpos - ci.curpos; - if (difference >= 0) + while (length) { - /* Seeking forward */ - logf("seek: +%d", difference); - codec_advance_buffer_callback(difference); - return true; - } - - /* Seeking backward */ - difference = -difference; - if (ci.curpos - difference < 0) - difference = ci.curpos; + long est_output_size = dsp_output_size(length); + /* Prevent audio from a previous track from playing */ + if (ci.new_track || ci.stop_codec) + return true; - /* We need to reload the song. */ - if (newpos < cur_ti->start_pos) - { - struct event ev; - - LOGFQUEUE("codec > audio Q_AUDIO_REBUFFER_SEEK"); - queue_post(&audio_queue, Q_AUDIO_REBUFFER_SEEK, (void *)newpos); - - queue_wait(&codec_callback_queue, &ev); - switch (ev.id) + while ((dest = pcmbuf_request_buffer(est_output_size, + &output_size)) == NULL) { - case Q_CODEC_REQUEST_COMPLETE: - LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); + sleep(1); + if (ci.seek_time || ci.new_track || ci.stop_codec) return true; - - case Q_CODEC_REQUEST_FAILED: - LOGFQUEUE("codec < Q_CODEC_REQUEST_FAILED"); - ci.stop_codec = true; - return false; - - default: - LOGFQUEUE("codec < default"); - return false; } - } - /* Seeking inside buffer space. */ - logf("seek: -%d", difference); - filebufused += difference; - cur_ti->available += difference; - if (buf_ridx < (unsigned)difference) - buf_ridx += filebuflen; - buf_ridx -= difference; - ci.curpos -= difference; + /* Get the real input_size for output_size bytes, guarding + * against resampling buffer overflows. */ + input_size = dsp_input_size(output_size); - return true; -} + if (input_size <= 0) + { + DEBUGF("Error: dsp_input_size(%ld=dsp_output_size(%ld))=%ld<=0\n", + output_size, length, input_size); + /* If this happens, there are samples of codec data that don't + * become a number of pcm samples, and something is broken */ + return false; + } -static void set_filebuf_watermark(int seconds) -{ - size_t bytes; + /* Input size has grown, no error, just don't write more than length */ + if ((size_t)input_size > length) + input_size = length; - if (current_codec == CODEC_IDX_VOICE) - return; + output_size = dsp_process(dest, src, input_size); - if (!filebuf) - return; /* Audio buffers not yet set up */ + pcmbuf_write_complete(output_size); - bytes = MAX(cur_ti->id3.bitrate * seconds * (1000/8), conf_watermark); - bytes = MIN(bytes, filebuflen / 2); - conf_watermark = bytes; -} + if (voice_is_playing && pcm_is_playing() && + pcmbuf_usage() > 30 && pcmbuf_mix_free() > 80) + { + swap_codec(); + } + + length -= input_size; + } -static void codec_configure_callback(int setting, void *value) -{ - switch (setting) { - case CODEC_SET_FILEBUF_WATERMARK: - conf_watermark = (unsigned long)value; - set_filebuf_watermark(buffer_margin); - break; + return true; +} /* codec_pcmbuf_insert_split_callback */ - case CODEC_SET_FILEBUF_CHUNKSIZE: - conf_filechunk = (unsigned long)value; - break; +static bool codec_pcmbuf_insert_callback(const char *buf, size_t length) +{ + /* TODO: The audiobuffer API should probably be updated, and be based on + * pcmbuf_insert_split(). */ + long real_length = length; - case CODEC_SET_FILEBUF_PRESEEK: - conf_preseek = (unsigned long)value; - break; + if (dsp_stereo_mode() == STEREO_NONINTERLEAVED) + length /= 2; /* Length is per channel */ - default: - if (!dsp_configure(setting, value)) { logf("Illegal key:%d", setting); } - } + /* Second channel is only used for non-interleaved stereo. */ + return codec_pcmbuf_insert_split_callback(buf, buf + (real_length / 2), + length); } -void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3, - bool last_track)) +static void* codec_get_memory_callback(size_t *size) { - track_buffer_callback = handler; + *size = MALLOC_BUFSIZE; + return &audiobuf[talk_get_bufsize()]; } -void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3, - bool last_track)) +static void codec_pcmbuf_position_callback(size_t size) ICODE_ATTR; +static void codec_pcmbuf_position_callback(size_t size) { - track_unbuffer_callback = handler; + unsigned int time = size * 1000 / 4 / NATIVE_FREQUENCY + + prev_ti->id3.elapsed; + + if (time >= prev_ti->id3.length) + { + pcmbuf_set_position_callback(NULL); + prev_ti->id3.elapsed = prev_ti->id3.length; + } + else + prev_ti->id3.elapsed = time; } -void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3)) +static void codec_set_elapsed_callback(unsigned int value) { - track_changed_callback = handler; -} + unsigned int latency; + if (ci.seek_time) + return; -static void codec_track_changed(void) -{ - automatic_skip = false; - track_changed = true; - LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED"); - queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); +#ifdef AB_REPEAT_ENABLE + ab_position_report(value); +#endif + + latency = pcmbuf_get_latency(); + if (value < latency) + cur_ti->id3.elapsed = 0; + else if (value - latency > cur_ti->id3.elapsed || + value - latency < cur_ti->id3.elapsed - 2) + { + cur_ti->id3.elapsed = value - latency; + } } -static void pcmbuf_track_changed_callback(void) +static void codec_set_offset_callback(size_t value) { - pcmbuf_set_position_callback(NULL); - codec_track_changed(); -} - -/* Yield to codecs for as long as possible if they are in need of data - * return true if the caller should break to let the audio thread process - * new events */ -static bool audio_yield_codecs(void) -{ - yield(); + unsigned int latency; - if (!queue_empty(&audio_queue)) return true; + if (ci.seek_time) + return; - while ((pcmbuf_is_crossfade_active() || pcmbuf_is_lowdata()) - && !ci.stop_codec && playing && !filebuf_is_lowdata()) - { - sleep(1); - if (!queue_empty(&audio_queue)) return true; - } - - return false; + latency = pcmbuf_get_latency() * cur_ti->id3.bitrate / 8; + if (value < latency) + cur_ti->id3.offset = 0; + else + cur_ti->id3.offset = value - latency; } -/* FIXME: This code should be made more generic and move to metadata.c */ -static void strip_id3v1_tag(void) +static void codec_advance_buffer_counters(size_t amount) { - int i; - static const unsigned char tag[] = "TAG"; - size_t tag_idx; - size_t cur_idx; - - tag_idx = buf_widx; - if (tag_idx < 128) - tag_idx += filebuflen; - tag_idx -= 128; + buf_ridx += amount; + + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; + + ci.curpos += amount; + cur_ti->available -= amount; + filebufused -= amount; - if (filebufused > 128 && tag_idx > buf_ridx) + /* Start buffer filling as necessary. */ + if (!pcmbuf_is_lowdata() && !filling) { - cur_idx = tag_idx; - for(i = 0;i < 3;i++) + if (conf_watermark && filebufused <= conf_watermark && playing) { - if(filebuf[cur_idx] != tag[i]) - return; - - if(++cur_idx >= filebuflen) - cur_idx -= filebuflen; + LOGFQUEUE("codec > audio Q_AUDIO_FILL_BUFFER"); + queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); } - - /* Skip id3v1 tag */ - logf("Skipping ID3v1 tag"); - buf_widx = tag_idx; - tracks[track_widx].available -= 128; - tracks[track_widx].filesize -= 128; - filebufused -= 128; } } -static void audio_read_file(bool quick) +/* copy up-to size bytes into ptr and return the actual size copied */ +static size_t codec_filebuf_callback(void *ptr, size_t size) { + char *buf = (char *)ptr; size_t copy_n; - int rc; - - /* If we're called and no file is open, this is an error */ - if (current_fd < 0) - { - logf("Bad fd in arf"); - /* Stop this buffer cycle immediately */ - fill_bytesleft = 0; - /* Give some hope of miraculous recovery by forcing a track reload */ - tracks[track_widx].filesize = 0; - return ; - } - - while (tracks[track_widx].filerem > 0) - { - int overlap; + size_t part_n; - if (fill_bytesleft == 0) - break ; + if (ci.stop_codec || !playing) + return 0; - /* copy_n is the largest chunk that is safe to read */ - copy_n = MIN(conf_filechunk, filebuflen - buf_widx); - copy_n = MIN(copy_n, fill_bytesleft); + /* The ammount to copy is the lesser of the requested amount and the + * amount left of the current track (both on disk and already loaded) */ + copy_n = MIN(size, cur_ti->available + cur_ti->filerem); - /* rc is the actual amount read */ - rc = read(current_fd, &filebuf[buf_widx], copy_n); + /* Nothing requested OR nothing left */ + if (copy_n == 0) + return 0; - if (rc <= 0) + /* Let the disk buffer catch fill until enough data is available */ + while (copy_n > cur_ti->available) + { + if (!filling) { - /* Reached the end of the file */ - tracks[track_widx].filerem = 0; - break ; - } - - buf_widx += rc; - - overlap = buf_widx - tracks[track_ridx].buf_idx; - if (buf_widx >= filebuflen) - buf_widx -= filebuflen; - if (overlap > 0 && (unsigned) overlap >= filebuflen) - overlap -= filebuflen; - - if (overlap > 0 && overlap <= rc && tracks[track_ridx].available != 0) { - tracks[track_ridx].buf_idx = buf_widx; - tracks[track_ridx].start_pos += overlap; + LOGFQUEUE("codec > audio Q_AUDIO_FILL_BUFFER"); + queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); } - tracks[track_widx].available += rc; - tracks[track_widx].filerem -= rc; - - filebufused += rc; - if (fill_bytesleft > (unsigned)rc) - fill_bytesleft -= rc; - else - fill_bytesleft = 0; + sleep(1); + if (ci.stop_codec || ci.new_track) + return 0; + } - /* Let the codec process until it is out of the danger zone, or there - * is an event to handle. In the latter case, break this fill cycle - * immediately */ - if (quick || audio_yield_codecs()) - break; + /* Copy as much as possible without wrapping */ + part_n = MIN(copy_n, filebuflen - buf_ridx); + memcpy(buf, &filebuf[buf_ridx], part_n); + /* Copy the rest in the case of a wrap */ + if (part_n < copy_n) { + memcpy(&buf[part_n], &filebuf[0], copy_n - part_n); } - if (tracks[track_widx].filerem == 0) - { - logf("Finished buf:%dB", tracks[track_widx].filesize); - close(current_fd); - current_fd = -1; - strip_id3v1_tag(); + /* Update read and other position pointers */ + codec_advance_buffer_counters(copy_n); - track_widx++; - track_widx &= MAX_TRACK_MASK; + /* Return the actual amount of data copied to the buffer */ + return copy_n; +} /* codec_filebuf_callback */ - tracks[track_widx].filesize = 0; - } - else +static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) +{ + size_t short_n, copy_n, buf_rem; + + if (!playing) { - logf("Partially buf:%dB", - tracks[track_widx].filesize - tracks[track_widx].filerem); + *realsize = 0; + return NULL; } -} -static void codec_discard_codec_callback(void) -{ - if (cur_ti->has_codec) + copy_n = MIN(reqsize, cur_ti->available + cur_ti->filerem); + if (copy_n == 0) { - cur_ti->has_codec = false; - filebufused -= cur_ti->codecsize; - buf_ridx += cur_ti->codecsize; - if (buf_ridx >= filebuflen) - buf_ridx -= filebuflen; + *realsize = 0; + return NULL; } -#if 0 - /* Check if a buffer desync has happened, log it and stop playback. */ - if (buf_ridx != cur_ti->buf_idx) + while (copy_n > cur_ti->available) { - int offset = cur_ti->buf_idx - buf_ridx; - size_t new_used = filebufused - offset; + if (!filling) + { + LOGFQUEUE("codec > audio Q_AUDIO_FILL_BUFFER"); + queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); + } - logf("Buf off :%d=%d-%d", offset, cur_ti->buf_idx, buf_ridx); - logf("Used off:%d",filebufused - new_used); + sleep(1); + if (ci.stop_codec || ci.new_track) + { + *realsize = 0; + return NULL; + } + } + + /* How much is left at the end of the file buffer before wrap? */ + buf_rem = filebuflen - buf_ridx; + + /* If we can't satisfy the request without wrapping */ + if (buf_rem < copy_n) + { + /* How short are we? */ + short_n = copy_n - buf_rem; - /* This is a fatal internal error and it's not safe to - * continue playback. */ - ci.stop_codec = true; - queue_post(&audio_queue, Q_AUDIO_STOP, 0); + /* If we can fudge it with the guardbuf */ + if (short_n < GUARD_BUFSIZE) + memcpy(&filebuf[filebuflen], &filebuf[0], short_n); + else + copy_n = buf_rem; } -#endif -} -static const char * get_codec_filename(int enc_spec) + *realsize = copy_n; + + return (char *)&filebuf[buf_ridx]; +} /* codec_request_buffer_callback */ + +static int get_codec_base_type(int type) { - const char *fname; - int type = enc_spec & CODEC_TYPE_MASK; - int afmt = enc_spec & CODEC_AFMT_MASK; + switch (type) { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + return AFMT_MPA_L3; + } - if ((unsigned)afmt >= AFMT_NUM_CODECS) - type = AFMT_UNKNOWN | (type & CODEC_TYPE_MASK); + return type; +} - fname = (type == CODEC_TYPE_DECODER) ? - audio_formats[afmt].codec_fn : audio_formats[afmt].codec_enc_fn; +static void codec_advance_buffer_callback(size_t amount) +{ + if (amount > cur_ti->available + cur_ti->filerem) + amount = cur_ti->available + cur_ti->filerem; - logf("%s: %d - %s", - (type == CODEC_TYPE_ENCODER) ? "Encoder" : "Decoder", - afmt, fname ? fname : ""); + while (amount > cur_ti->available && filling) + sleep(1); - return fname; -} /* get_codec_filename */ - -static bool audio_loadcodec(bool start_play) -{ - size_t size; - int fd; - int rc; - size_t copy_n; - int prev_track; - char codec_path[MAX_PATH]; /* Full path to codec */ - - const char * codec_fn = get_codec_filename(tracks[track_widx].id3.codectype); - if (codec_fn == NULL) - return false; - - tracks[track_widx].has_codec = false; - tracks[track_widx].codecsize = 0; - - if (start_play) - { - /* Load the codec directly from disk and save some memory. */ - track_ridx = track_widx; - cur_ti = &tracks[track_ridx]; - ci.filesize = cur_ti->filesize; - ci.id3 = &cur_ti->id3; - ci.taginfo_ready = &cur_ti->taginfo_ready; - ci.curpos = 0; - playing = true; - LOGFQUEUE("codec > codec Q_CODEC_LOAD_DISK"); - queue_post(&codec_queue, Q_CODEC_LOAD_DISK, (void *)codec_fn); - return true; - } - else + if (amount > cur_ti->available) { - /* If we already have another track than this one buffered */ - if (track_widx != track_ridx) + struct event ev; + + LOGFQUEUE("codec > audio Q_AUDIO_REBUFFER_SEEK"); + queue_post(&audio_queue, + Q_AUDIO_REBUFFER_SEEK, (void *)(ci.curpos + amount)); + + queue_wait(&codec_callback_queue, &ev); + switch (ev.id) { - prev_track = (track_widx - 1) & MAX_TRACK_MASK; - - /* If the previous codec is the same as this one, there is no need - * to put another copy of it on the file buffer */ - if (get_codec_base_type(tracks[track_widx].id3.codectype) == - get_codec_base_type(tracks[prev_track].id3.codectype) - && audio_codec_loaded) - { - logf("Reusing prev. codec"); - return true; - } + case Q_CODEC_REQUEST_FAILED: + LOGFQUEUE("codec < Q_CODEC_REQUEST_FAILED"); + ci.stop_codec = true; + return; + + case Q_CODEC_REQUEST_COMPLETE: + LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); + return; + + default: + LOGFQUEUE("codec < default"); + ci.stop_codec = true; + return; } } - codec_get_full_path(codec_path, codec_fn); + codec_advance_buffer_counters(amount); - fd = open(codec_path, O_RDONLY); - if (fd < 0) - { - logf("Codec doesn't exist!"); - return false; - } + codec_set_offset_callback(ci.curpos); +} - size = filesize(fd); +static void codec_advance_buffer_loc_callback(void *ptr) +{ + size_t amount = (size_t)ptr - (size_t)&filebuf[buf_ridx]; - /* Never load a partial codec */ - if (fill_bytesleft < size) - { - logf("Not enough space"); - fill_bytesleft = 0; - close(fd); - return false; - } + codec_advance_buffer_callback(amount); +} - while (tracks[track_widx].codecsize < size) +/* Copied from mpeg.c. Should be moved somewhere else. */ +static int codec_get_file_pos(void) +{ + int pos = -1; + struct mp3entry *id3 = audio_current_track(); + + if (id3->vbr) { - copy_n = MIN(conf_filechunk, filebuflen - buf_widx); - rc = read(fd, &filebuf[buf_widx], copy_n); - if (rc < 0) - return false; - - filebufused += rc; - if (fill_bytesleft > (unsigned)rc) - fill_bytesleft -= rc; - else - fill_bytesleft = 0; + if (id3->has_toc) + { + /* Use the TOC to find the new position */ + unsigned int percent, remainder; + int curtoc, nexttoc, plen; - buf_widx += rc; - if (buf_widx >= filebuflen) - buf_widx -= filebuflen; + percent = (id3->elapsed*100)/id3->length; + if (percent > 99) + percent = 99; - tracks[track_widx].codecsize += rc; - - audio_yield_codecs(); - } + curtoc = id3->toc[percent]; - tracks[track_widx].has_codec = true; + if (percent < 99) + nexttoc = id3->toc[percent+1]; + else + nexttoc = 256; - close(fd); - logf("Done: %dB", size); + pos = (id3->filesize/256)*curtoc; - return true; -} + /* Use the remainder to get a more accurate position */ + remainder = (id3->elapsed*100)%id3->length; + remainder = (remainder*100)/id3->length; + plen = (nexttoc - curtoc)*(id3->filesize/256); + pos += (plen/100)*remainder; + } + else + { + /* No TOC exists, estimate the new position */ + pos = (id3->filesize / (id3->length / 1000)) * + (id3->elapsed / 1000); + } + } + else if (id3->bitrate) + pos = id3->elapsed * (id3->bitrate / 8); + else + return -1; -static bool read_next_metadata(void) -{ - int fd; - char *trackname; - int next_idx; - int status; + /* Don't seek right to the end of the file so that we can + transition properly to the next song */ + if (pos >= (int)(id3->filesize - id3->id3v1len)) + pos = id3->filesize - id3->id3v1len - 1; + /* skip past id3v2 tag and other leading garbage */ + else if (pos < (int)id3->first_frame_offset) + pos = id3->first_frame_offset; - next_idx = track_widx; - if (tracks[next_idx].taginfo_ready) - { - next_idx++; - next_idx &= MAX_TRACK_MASK; + return pos; +} - if (tracks[next_idx].taginfo_ready) - return true; - } +static off_t codec_mp3_get_filepos_callback(int newtime) +{ + off_t newpos; - trackname = playlist_peek(last_peek_offset + 1); - if (!trackname) - return false; + cur_ti->id3.elapsed = newtime; + newpos = codec_get_file_pos(); - fd = open(trackname, O_RDONLY); - if (fd < 0) - return false; + return newpos; +} - status = get_metadata(&tracks[next_idx],fd,trackname,v1first); - /* Preload the glyphs in the tags */ - if (status) +static void codec_seek_complete_callback(void) +{ + logf("seek_complete"); + if (pcm_is_paused()) { - if (tracks[next_idx].id3.title) - lcd_getstringsize(tracks[next_idx].id3.title, NULL, NULL); - if (tracks[next_idx].id3.artist) - lcd_getstringsize(tracks[next_idx].id3.artist, NULL, NULL); - if (tracks[next_idx].id3.album) - lcd_getstringsize(tracks[next_idx].id3.album, NULL, NULL); + /* If this is not a seamless seek, clear the buffer */ + pcmbuf_play_stop(); + + /* If playback was not 'deliberately' paused, unpause now */ + if (!paused) + pcmbuf_pause(false); } - close(fd); - - return status; + ci.seek_time = 0; } -static bool audio_load_track(int offset, bool start_play, bool rebuffer) +static bool codec_seek_buffer_callback(size_t newpos) { - char *trackname; - off_t size; - char msgbuf[80]; + int difference; - /* Stop buffer filling if there is no free track entries. - Don't fill up the last track entry (we wan't to store next track - metadata there). */ - if (!have_free_tracks()) - { - logf("No free tracks"); - return false; - } + logf("codec_seek_buffer_callback"); - if (current_fd >= 0) - { - logf("Nonzero fd in alt"); - close(current_fd); - current_fd = -1; - } + if (newpos >= cur_ti->filesize) + newpos = cur_ti->filesize - 1; - last_peek_offset++; - peek_again: - logf("Buffering track:%d/%d", track_widx, track_ridx); - /* Get track name from current playlist read position. */ - while ((trackname = playlist_peek(last_peek_offset)) != NULL) + difference = newpos - ci.curpos; + if (difference >= 0) { - /* Handle broken playlists. */ - current_fd = open(trackname, O_RDONLY); - if (current_fd < 0) - { - logf("Open failed"); - /* Skip invalid entry from playlist. */ - playlist_skip_entry(NULL, last_peek_offset); - } - else - break; + /* Seeking forward */ + logf("seek: +%d", difference); + codec_advance_buffer_callback(difference); + return true; } - if (!trackname) - { - logf("End-of-playlist"); - playlist_end = true; - return false; - } - - /* Initialize track entry. */ - size = filesize(current_fd); - tracks[track_widx].filerem = size; - tracks[track_widx].filesize = size; - tracks[track_widx].available = 0; + /* Seeking backward */ + difference = -difference; + if (ci.curpos - difference < 0) + difference = ci.curpos; - /* Set default values */ - if (start_play) + /* We need to reload the song. */ + if (newpos < cur_ti->start_pos) { - int last_codec = current_codec; + struct event ev; - current_codec = CODEC_IDX_AUDIO; - conf_watermark = AUDIO_DEFAULT_WATERMARK; - conf_filechunk = AUDIO_DEFAULT_FILECHUNK; - conf_preseek = AUDIO_REBUFFER_GUESS_SIZE; - dsp_configure(DSP_RESET, 0); - current_codec = last_codec; - } - - /* Get track metadata if we don't already have it. */ - if (!tracks[track_widx].taginfo_ready) - { - if (get_metadata(&tracks[track_widx],current_fd,trackname,v1first)) - { - if (start_play) - { - track_changed = true; - playlist_update_resume_info(audio_current_track()); - } - } - else + LOGFQUEUE("codec > audio Q_AUDIO_REBUFFER_SEEK"); + queue_post(&audio_queue, Q_AUDIO_REBUFFER_SEEK, (void *)newpos); + + queue_wait(&codec_callback_queue, &ev); + switch (ev.id) { - logf("mde:%s!",trackname); + case Q_CODEC_REQUEST_COMPLETE: + LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); + return true; - /* Set filesize to zero to indicate no file was loaded. */ - tracks[track_widx].filesize = 0; - tracks[track_widx].filerem = 0; - close(current_fd); - current_fd = -1; + case Q_CODEC_REQUEST_FAILED: + LOGFQUEUE("codec < Q_CODEC_REQUEST_FAILED"); + ci.stop_codec = true; + return false; - /* Skip invalid entry from playlist. */ - playlist_skip_entry(NULL, last_peek_offset); - tracks[track_widx].taginfo_ready = false; - goto peek_again; + default: + LOGFQUEUE("codec < default"); + return false; } - } - /* Load the codec. */ - tracks[track_widx].codecbuf = &filebuf[buf_widx]; - if (!audio_loadcodec(start_play)) - { - if (tracks[track_widx].codecsize) - { - /* Must undo the buffer write of the partial codec */ - logf("Partial codec loaded"); - fill_bytesleft += tracks[track_widx].codecsize; - filebufused -= tracks[track_widx].codecsize; - if (buf_widx < tracks[track_widx].codecsize) - buf_widx += filebuflen; - buf_widx -= tracks[track_widx].codecsize; - tracks[track_widx].codecsize = 0; - } - - /* Set filesize to zero to indicate no file was loaded. */ - tracks[track_widx].filesize = 0; - tracks[track_widx].filerem = 0; - close(current_fd); - current_fd = -1; - - /* Try skipping to next track if there is space. */ - if (fill_bytesleft > 0) - { - /* This is an error condition unless the fill_bytesleft is 0 */ - snprintf(msgbuf, sizeof(msgbuf)-1, "No codec for: %s", trackname); - /* We should not use gui_syncplash from audio thread! */ - gui_syncsplash(HZ*2, true, msgbuf); - /* Skip invalid entry from playlist. */ - playlist_skip_entry(NULL, last_peek_offset); - tracks[track_widx].taginfo_ready = false; - goto peek_again; - } - - return false; - } + /* Seeking inside buffer space. */ + logf("seek: -%d", difference); + filebufused += difference; + cur_ti->available += difference; + if (buf_ridx < (unsigned)difference) + buf_ridx += filebuflen; + buf_ridx -= difference; + ci.curpos -= difference; - tracks[track_widx].start_pos = 0; - set_filebuf_watermark(buffer_margin); - tracks[track_widx].id3.elapsed = 0; + return true; +} - if (offset > 0) - { - switch (tracks[track_widx].id3.codectype) { - case AFMT_MPA_L1: - case AFMT_MPA_L2: - case AFMT_MPA_L3: - lseek(current_fd, offset, SEEK_SET); - tracks[track_widx].id3.offset = offset; - mp3_set_elapsed(&tracks[track_widx].id3); - tracks[track_widx].filerem = size - offset; - ci.curpos = offset; - tracks[track_widx].start_pos = offset; - break; +static void codec_configure_callback(int setting, void *value) +{ + switch (setting) { + case CODEC_SET_FILEBUF_WATERMARK: + conf_watermark = (unsigned long)value; + set_filebuf_watermark(buffer_margin); + break; - case AFMT_WAVPACK: - lseek(current_fd, offset, SEEK_SET); - tracks[track_widx].id3.offset = offset; - tracks[track_widx].id3.elapsed = - tracks[track_widx].id3.length / 2; - tracks[track_widx].filerem = size - offset; - ci.curpos = offset; - tracks[track_widx].start_pos = offset; - break; + case CODEC_SET_FILEBUF_CHUNKSIZE: + conf_filechunk = (unsigned long)value; + break; - case AFMT_OGG_VORBIS: - case AFMT_FLAC: - case AFMT_PCM_WAV: - case AFMT_A52: - case AFMT_AAC: - tracks[track_widx].id3.offset = offset; - break; - } + case CODEC_SET_FILEBUF_PRESEEK: + conf_preseek = (unsigned long)value; + break; + default: + if (!dsp_configure(setting, value)) { logf("Illegal key:%d", setting); } } - - logf("alt:%s", trackname); - tracks[track_widx].buf_idx = buf_widx; +} - audio_read_file(rebuffer); +static void codec_track_changed(void) +{ + automatic_skip = false; + track_changed = true; + LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED"); + queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); +} - return true; +static void codec_pcmbuf_track_changed_callback(void) +{ + pcmbuf_set_position_callback(NULL); + codec_track_changed(); } -/* Note that this function might yield(). */ -static void audio_clear_track_entries( - bool clear_buffered, bool clear_unbuffered, - bool may_yield) +static void codec_discard_codec_callback(void) { - int cur_idx = track_widx; - int last_idx = -1; - - logf("Clearing tracks:%d/%d, %d", track_ridx, track_widx, clear_unbuffered); - - /* Loop over all tracks from write-to-read */ - while (1) + if (cur_ti->has_codec) { - cur_idx++; - cur_idx &= MAX_TRACK_MASK; - - if (cur_idx == track_ridx) - break; - - /* If the track is buffered, conditionally clear/notify, - * otherwise clear the track if that option is selected */ - if (tracks[cur_idx].event_sent) - { - if (clear_buffered) - { - if (last_idx >= 0) - { - /* If there is an unbuffer callback, call it, otherwise, - * just clear the track */ - if (track_unbuffer_callback) - { - if (may_yield) - audio_yield_codecs(); - track_unbuffer_callback(&tracks[last_idx].id3, false); - } - - memset(&tracks[last_idx], 0, sizeof(struct track_info)); - } - last_idx = cur_idx; - } - } - else if (clear_unbuffered) - memset(&tracks[cur_idx], 0, sizeof(struct track_info)); + cur_ti->has_codec = false; + filebufused -= cur_ti->codecsize; + buf_ridx += cur_ti->codecsize; + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; } - /* We clear the previous instance of a buffered track throughout - * the above loop to facilitate 'last' detection. Clear/notify - * the last track here */ - if (last_idx >= 0) +#if 0 + /* Check if a buffer desync has happened, log it and stop playback. */ + if (buf_ridx != cur_ti->buf_idx) { - if (track_unbuffer_callback) - track_unbuffer_callback(&tracks[last_idx].id3, true); - memset(&tracks[last_idx], 0, sizeof(struct track_info)); + int offset = cur_ti->buf_idx - buf_ridx; + size_t new_used = filebufused - offset; + + logf("Buf off :%d=%d-%d", offset, cur_ti->buf_idx, buf_ridx); + logf("Used off:%d",filebufused - new_used); + + /* This is a fatal internal error and it's not safe to + * continue playback. */ + ci.stop_codec = true; + queue_post(&audio_queue, Q_AUDIO_STOP, 0); } +#endif } -static void audio_stop_codec_flush(void) -{ - ci.stop_codec = true; - pcmbuf_pause(true); - while (audio_codec_loaded) - yield(); - /* If the audio codec is not loaded any more, and the audio is still - * playing, it is now and _only_ now safe to call this function from the - * audio thread */ - if (pcm_is_playing()) - pcmbuf_play_stop(); - pcmbuf_pause(paused); -} - -static void audio_stop_playback(void) -{ - /* If we were playing, save resume information */ - if (playing) - { - /* Save the current playing spot, or NULL if the playlist has ended */ - playlist_update_resume_info( - (playlist_end && ci.stop_codec)?NULL:audio_current_track()); - } - - while (voice_is_playing && !queue_empty(&voice_codec_queue)) - yield(); - - filebufused = 0; - playing = false; - filling = false; - paused = false; - audio_stop_codec_flush(); - - if (current_fd >= 0) - { - close(current_fd); - current_fd = -1; - } - - /* Mark all entries null. */ - audio_clear_track_entries(true, false, false); - memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); -} - -static void audio_play_start(size_t offset) -{ -#if defined(HAVE_RECORDING) || defined(CONFIG_TUNER) - rec_set_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); -#endif - - /* Wait for any previously playing audio to flush - TODO: Not necessary? */ - while (audio_codec_loaded) - audio_stop_codec_flush(); - - track_changed = true; - playlist_end = false; - - playing = true; - ci.new_track = 0; - ci.seek_time = 0; - - if (current_fd >= 0) - { - close(current_fd); - current_fd = -1; - } - - sound_set_volume(global_settings.volume); - track_widx = track_ridx = 0; - buf_ridx = buf_widx = 0; - filebufused = 0; - - /* Mark all entries null. */ - memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); - - last_peek_offset = -1; - - audio_fill_file_buffer(true, false, offset); -} - -/* Send callback events to notify about new tracks. */ -static void audio_generate_postbuffer_events(void) -{ - int cur_idx; - int last_idx = -1; - - logf("Postbuffer:%d/%d",track_ridx,track_widx); - - if (have_tracks()) - { - cur_idx = track_ridx; - - while (1) { - if (!tracks[cur_idx].event_sent) - { - if (last_idx >= 0 && !tracks[last_idx].event_sent) - { - /* Mark the event 'sent' even if we don't really send one */ - tracks[last_idx].event_sent = true; - if (track_buffer_callback) - track_buffer_callback(&tracks[last_idx].id3, false); - } - last_idx = cur_idx; - } - if (cur_idx == track_widx) - break; - cur_idx++; - cur_idx &= MAX_TRACK_MASK; - } - - if (last_idx >= 0 && !tracks[last_idx].event_sent) - { - tracks[last_idx].event_sent = true; - if (track_buffer_callback) - track_buffer_callback(&tracks[last_idx].id3, true); - } - - /* Force WPS reload. */ - track_changed = true; - } -} - -static bool audio_initialize_buffer_fill(bool clear_tracks) -{ - /* Don't initialize if we're already initialized */ - if (filling) - return true; - - /* Don't start buffer fill if buffer is already full. */ - if (filebufused > conf_watermark && !filling) - return false; - - logf("Starting buffer fill"); - - fill_bytesleft = filebuflen - filebufused; - /* TODO: This doesn't look right, and might explain some problems with - * seeking in large files (to offsets larger than filebuflen). - * And what about buffer wraps? - * - * This really doesn't look right, so don't use it. - */ - // if (buf_ridx > cur_ti->buf_idx) - // cur_ti->start_pos = buf_ridx - cur_ti->buf_idx; - - /* Set the filling flag true before calling audio_clear_tracks as that - * function can yield and we start looping. */ - filling = true; - - if (clear_tracks) - audio_clear_track_entries(true, false, true); - - /* Save the current resume position once. */ - playlist_update_resume_info(audio_current_track()); - - return true; -} - -static void audio_fill_file_buffer( - bool start_play, bool rebuffer, size_t offset) -{ - bool had_next_track = audio_next_track() != NULL; - - if (!audio_initialize_buffer_fill(!start_play)) - return ; - - /* If we have a partially buffered track, continue loading, - * otherwise load a new track */ - if (tracks[track_widx].filesize > 0) - audio_read_file(false); - else if (!audio_load_track(offset, start_play, rebuffer)) - fill_bytesleft = 0; - - if (!had_next_track && audio_next_track()) - track_changed = true; - - /* If we're done buffering */ - if (fill_bytesleft == 0) - { - read_next_metadata(); - - audio_generate_postbuffer_events(); - filling = false; - -#ifndef SIMULATOR - if (playing) - ata_sleep(); -#endif - } -} - -static void codec_track_skip_done(bool was_manual) +static void codec_track_skip_done(bool was_manual) { /* Manual track change (always crossfade or flush audio). */ if (was_manual) @@ -2202,8 +1582,8 @@ static void codec_track_skip_done(bool was_manual) /* Gapless playback. */ else { - pcmbuf_set_position_callback(pcmbuf_position_callback); - pcmbuf_set_event_handler(pcmbuf_track_changed_callback); + pcmbuf_set_position_callback(codec_pcmbuf_position_callback); + pcmbuf_set_event_handler(codec_pcmbuf_track_changed_callback); } } @@ -2261,12 +1641,6 @@ static bool codec_load_next_track(void) } } -static bool voice_request_next_track_callback(void) -{ - ci_voice.new_track = 0; - return true; -} - static bool codec_request_next_track_callback(void) { int prev_codectype; @@ -2293,254 +1667,60 @@ static bool codec_request_next_track_callback(void) } } -/* Invalidates all but currently playing track. */ -void audio_invalidate_tracks(void) +static void codec_thread(void) { - if (have_tracks()) { - last_peek_offset = 0; - - playlist_end = false; - track_widx = track_ridx; - audio_clear_track_entries(true, true, true); - - /* If the current track is fully buffered, advance the write pointer */ - if (tracks[track_widx].filerem == 0) - track_widx = (track_widx + 1) & MAX_TRACK_MASK; + struct event ev; + int status; + size_t wrap; - /* Mark all other entries null (also buffered wrong metadata). */ - filebufused = cur_ti->available; - buf_widx = buf_ridx + cur_ti->available; - if (buf_widx >= filebuflen) - buf_widx -= filebuflen; + while (1) { + status = 0; + queue_wait(&codec_queue, &ev); - read_next_metadata(); - } -} + switch (ev.id) { + case Q_CODEC_LOAD_DISK: + LOGFQUEUE("codec < Q_CODEC_LOAD_DISK"); + audio_codec_loaded = true; + if (voice_codec_loaded) + { + LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); + queue_post(&voice_queue, Q_AUDIO_PLAY, 0); + } + mutex_lock(&mutex_codecthread); + current_codec = CODEC_IDX_AUDIO; + ci.stop_codec = false; + status = codec_load_file((const char *)ev.data, &ci); + mutex_unlock(&mutex_codecthread); + break ; -static void audio_new_playlist(void) -{ - /* Prepare to start a new fill from the beginning of the playlist */ - last_peek_offset = -1; - if (have_tracks()) { - playlist_end = false; - track_widx = track_ridx; - audio_clear_track_entries(true, true, true); + case Q_CODEC_LOAD: + LOGFQUEUE("codec < Q_CODEC_LOAD"); + if (!cur_ti->has_codec) { + logf("Codec slot is empty!"); + /* Wait for the pcm buffer to go empty */ + while (pcm_is_playing()) + yield(); + /* This must be set to prevent an infinite loop */ + ci.stop_codec = true; + LOGFQUEUE("codec > codec Q_AUDIO_PLAY"); + queue_post(&codec_queue, Q_AUDIO_PLAY, 0); + break ; + } - track_widx++; - track_widx &= MAX_TRACK_MASK; - - /* Stop reading the current track */ - cur_ti->filerem = 0; - close(current_fd); - current_fd = -1; - - /* Mark the current track as invalid to prevent skipping back to it */ - cur_ti->taginfo_ready = false; - - /* Invalidate the buffer other than the playing track */ - filebufused = cur_ti->available; - buf_widx = buf_ridx + cur_ti->available; - if (buf_widx >= filebuflen) - buf_widx -= filebuflen; - } - - /* Signal the codec to initiate a track change forward */ - new_playlist = true; - ci.new_track = 1; - audio_fill_file_buffer(false, true, 0); -} - -static void audio_initiate_track_change(long direction) -{ - if (playlist_check(direction)) - { - playlist_end = false; - /* Flag track changed immediately so wps can update instantly. - * No need to wait for disk to spin up or message to travel - * through the deep queues as this info is only for the wps. */ - track_changed = true; - ci.new_track += direction; - } -} - -static void audio_initiate_dir_change(long direction) -{ - playlist_end = false; - dir_skip = true; - ci.new_track = direction; -} - -void audio_thread(void) -{ - struct event ev; - - /* At first initialize audio system in background. */ - playback_init(); - - while (1) { - if (filling) - { - queue_wait_w_tmo(&audio_queue, &ev, 0); - if (ev.id == SYS_TIMEOUT) - ev.id = Q_AUDIO_FILL_BUFFER; - } - else - queue_wait_w_tmo(&audio_queue, &ev, HZ); - - switch (ev.id) { - case Q_AUDIO_FILL_BUFFER: - LOGFQUEUE("audio < Q_AUDIO_FILL_BUFFER"); - if (!filling) - if (!playing || playlist_end || ci.stop_codec) - break; - audio_fill_file_buffer(false, false, 0); - break; - - case Q_AUDIO_PLAY: - LOGFQUEUE("audio < Q_AUDIO_PLAY"); - audio_clear_track_entries(true, false, true); - audio_play_start((size_t)ev.data); - break ; - - case Q_AUDIO_STOP: - LOGFQUEUE("audio < Q_AUDIO_STOP"); - audio_stop_playback(); - break ; - - case Q_AUDIO_PAUSE: - LOGFQUEUE("audio < Q_AUDIO_PAUSE"); - pcmbuf_pause((bool)ev.data); - paused = (bool)ev.data; - break ; - - case Q_AUDIO_SKIP: - LOGFQUEUE("audio < Q_AUDIO_SKIP"); - audio_initiate_track_change((long)ev.data); - break; - - case Q_AUDIO_PRE_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_PRE_FF_REWIND"); - if (!playing) - break; - pcmbuf_pause(true); - break; - - case Q_AUDIO_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_FF_REWIND"); - if (!playing) - break ; - ci.seek_time = (long)ev.data+1; - break ; - - case Q_AUDIO_REBUFFER_SEEK: - LOGFQUEUE("audio < Q_AUDIO_REBUFFER_SEEK"); - audio_rebuffer_and_seek((size_t)ev.data); - break; - - case Q_AUDIO_CHECK_NEW_TRACK: - LOGFQUEUE("audio < Q_AUDIO_CHECK_NEW_TRACK"); - audio_check_new_track(); - break; - - case Q_AUDIO_DIR_SKIP: - LOGFQUEUE("audio < Q_AUDIO_DIR_SKIP"); - playlist_end = false; - if (global_settings.beep) - pcmbuf_beep(5000, 100, 2500*global_settings.beep); - audio_initiate_dir_change((long)ev.data); - break; - - case Q_AUDIO_NEW_PLAYLIST: - LOGFQUEUE("audio < Q_AUDIO_NEW_PLAYLIST"); - audio_new_playlist(); - break; - - case Q_AUDIO_FLUSH: - LOGFQUEUE("audio < Q_AUDIO_FLUSH"); - audio_invalidate_tracks(); - break ; - - case Q_AUDIO_TRACK_CHANGED: - LOGFQUEUE("audio < Q_AUDIO_TRACK_CHANGED"); - if (track_changed_callback) - track_changed_callback(&cur_ti->id3); - track_changed = true; - playlist_update_resume_info(audio_current_track()); - break ; - -#ifndef SIMULATOR - case SYS_USB_CONNECTED: - LOGFQUEUE("audio < SYS_USB_CONNECTED"); - audio_stop_playback(); - usb_acknowledge(SYS_USB_CONNECTED_ACK); - usb_wait_for_disconnect(&audio_queue); - break ; -#endif - - case SYS_TIMEOUT: - LOGFQUEUE("audio < SYS_TIMEOUT"); - break; - - default: - LOGFQUEUE("audio < default"); - } - } -} - -static void codec_thread(void) -{ - struct event ev; - int status; - size_t wrap; - - while (1) { - status = 0; - queue_wait(&codec_queue, &ev); - - switch (ev.id) { - case Q_CODEC_LOAD_DISK: - LOGFQUEUE("codec < Q_CODEC_LOAD_DISK"); - audio_codec_loaded = true; - if (voice_codec_loaded) - { - LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); - queue_post(&voice_codec_queue, Q_AUDIO_PLAY, 0); - } - mutex_lock(&mutex_codecthread); - current_codec = CODEC_IDX_AUDIO; - ci.stop_codec = false; - status = codec_load_file((const char *)ev.data, &ci); - mutex_unlock(&mutex_codecthread); - break ; - - case Q_CODEC_LOAD: - LOGFQUEUE("codec < Q_CODEC_LOAD"); - if (!cur_ti->has_codec) { - logf("Codec slot is empty!"); - /* Wait for the pcm buffer to go empty */ - while (pcm_is_playing()) - yield(); - /* This must be set to prevent an infinite loop */ - ci.stop_codec = true; - LOGFQUEUE("codec > codec Q_AUDIO_PLAY"); - queue_post(&codec_queue, Q_AUDIO_PLAY, 0); - break ; - } - - audio_codec_loaded = true; - if (voice_codec_loaded) - { - LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); - queue_post(&voice_codec_queue, Q_AUDIO_PLAY, 0); - } - mutex_lock(&mutex_codecthread); - current_codec = CODEC_IDX_AUDIO; - ci.stop_codec = false; - wrap = (size_t)&filebuf[filebuflen] - (size_t)cur_ti->codecbuf; - status = codec_load_ram(cur_ti->codecbuf, cur_ti->codecsize, - &filebuf[0], wrap, &ci); - mutex_unlock(&mutex_codecthread); - break ; + audio_codec_loaded = true; + if (voice_codec_loaded) + { + LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); + queue_post(&voice_queue, Q_AUDIO_PLAY, 0); + } + mutex_lock(&mutex_codecthread); + current_codec = CODEC_IDX_AUDIO; + ci.stop_codec = false; + wrap = (size_t)&filebuf[filebuflen] - (size_t)cur_ti->codecbuf; + status = codec_load_ram(cur_ti->codecbuf, cur_ti->codecsize, + &filebuf[0], wrap, &ci); + mutex_unlock(&mutex_codecthread); + break ; #if defined(HAVE_RECORDING) && !defined(SIMULATOR) case Q_ENCODER_LOAD_DISK: @@ -2549,7 +1729,7 @@ static void codec_thread(void) if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE) { LOGFQUEUE("codec > voice Q_ENCODER_RECORD"); - queue_post(&voice_codec_queue, Q_ENCODER_RECORD, NULL); + queue_post(&voice_queue, Q_ENCODER_RECORD, NULL); } mutex_lock(&mutex_codecthread); current_codec = CODEC_IDX_AUDIO; @@ -2640,541 +1820,1301 @@ static void codec_thread(void) default: LOGFQUEUE("codec < default"); - } /* end switch */ + } /* end switch */ + } +} + + +/* --- Audio thread --- */ + +static bool audio_filebuf_is_lowdata(void) +{ + return filebufused < AUDIO_FILEBUF_CRITICAL; +} + +static bool audio_have_tracks(void) +{ + return track_ridx != track_widx || cur_ti->filesize; +} + +static bool audio_have_free_tracks(void) +{ + if (track_widx < track_ridx) + return track_widx + 1 < track_ridx; + else if (track_ridx == 0) + return track_widx < MAX_TRACK - 1; + + return true; +} + +int audio_track_count(void) +{ + if (audio_have_tracks()) + { + int relative_track_widx = track_widx; + + if (track_ridx > track_widx) + relative_track_widx += MAX_TRACK; + + return relative_track_widx - track_ridx + 1; + } + + return 0; +} + + +/* Count the data BETWEEN the selected tracks */ +static size_t audio_buffer_count_tracks(int from_track, int to_track) +{ + size_t amount = 0; + bool need_wrap = to_track < from_track; + + while (1) + { + if (++from_track >= MAX_TRACK) + { + from_track -= MAX_TRACK; + need_wrap = false; + } + + if (from_track >= to_track && !need_wrap) + break; + + amount += tracks[from_track].codecsize + tracks[from_track].filesize; + } + return amount; +} + +static bool audio_buffer_wind_forward(int new_track_ridx, int old_track_ridx) +{ + size_t amount; + + /* Start with the remainder of the previously playing track */ + amount = tracks[old_track_ridx].filesize - ci.curpos; + /* Then collect all data from tracks in between them */ + amount += audio_buffer_count_tracks(old_track_ridx, new_track_ridx); + + if (amount > filebufused) + return false; + + logf("bwf:%ldB",amount); + + /* Wind the buffer to the beginning of the target track or its codec */ + buf_ridx += amount; + filebufused -= amount; + + /* Check and handle buffer wrapping */ + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; + + return true; +} + +static bool audio_buffer_wind_backward(int new_track_ridx, int old_track_ridx) +{ + /* Available buffer data */ + size_t buf_back; + /* Start with the previously playing track's data and our data */ + size_t amount; + + buf_back = buf_ridx; + amount = ci.curpos; + if (buf_ridx < buf_widx) + buf_back += filebuflen; + buf_back -= buf_widx; + + /* If we're not just resetting the current track */ + if (new_track_ridx != old_track_ridx) + { + /* Need to wind to before the old track's codec and our filesize */ + amount += tracks[old_track_ridx].codecsize; + amount += tracks[new_track_ridx].filesize; + + /* Rewind the old track to its beginning */ + tracks[old_track_ridx].available = + tracks[old_track_ridx].filesize - tracks[old_track_ridx].filerem; + } + + /* If the codec was ever buffered */ + if (tracks[new_track_ridx].codecsize) + { + /* Add the codec to the needed size */ + amount += tracks[new_track_ridx].codecsize; + tracks[new_track_ridx].has_codec = true; + } + + /* Then collect all data from tracks between new and old */ + amount += audio_buffer_count_tracks(new_track_ridx, old_track_ridx); + + /* Do we have space to make this skip? */ + if (amount > buf_back) + return false; + + logf("bwb:%ldB",amount); + + /* Check and handle buffer wrapping */ + if (amount > buf_ridx) + buf_ridx += filebuflen; + /* Rewind the buffer to the beginning of the target track or its codec */ + buf_ridx -= amount; + filebufused += amount; + + /* Reset to the beginning of the new track */ + tracks[new_track_ridx].available = tracks[new_track_ridx].filesize; + + return true; +} + +static void audio_update_trackinfo(void) +{ + ci.filesize = cur_ti->filesize; + cur_ti->id3.elapsed = 0; + cur_ti->id3.offset = 0; + ci.id3 = &cur_ti->id3; + ci.curpos = 0; + ci.taginfo_ready = &cur_ti->taginfo_ready; +} + +/* Yield to codecs for as long as possible if they are in need of data + * return true if the caller should break to let the audio thread process + * new events */ +static bool audio_yield_codecs(void) +{ + yield(); + + if (!queue_empty(&audio_queue)) return true; + + while ((pcmbuf_is_crossfade_active() || pcmbuf_is_lowdata()) + && !ci.stop_codec && playing && !audio_filebuf_is_lowdata()) + { + sleep(1); + if (!queue_empty(&audio_queue)) return true; + } + + return false; +} + +/* Note that this function might yield(). */ +static void audio_clear_track_entries( + bool clear_buffered, bool clear_unbuffered, + bool may_yield) +{ + int cur_idx = track_widx; + int last_idx = -1; + + logf("Clearing tracks:%d/%d, %d", track_ridx, track_widx, clear_unbuffered); + + /* Loop over all tracks from write-to-read */ + while (1) + { + cur_idx++; + cur_idx &= MAX_TRACK_MASK; + + if (cur_idx == track_ridx) + break; + + /* If the track is buffered, conditionally clear/notify, + * otherwise clear the track if that option is selected */ + if (tracks[cur_idx].event_sent) + { + if (clear_buffered) + { + if (last_idx >= 0) + { + /* If there is an unbuffer callback, call it, otherwise, + * just clear the track */ + if (track_unbuffer_callback) + { + if (may_yield) + audio_yield_codecs(); + track_unbuffer_callback(&tracks[last_idx].id3, false); + } + + memset(&tracks[last_idx], 0, sizeof(struct track_info)); + } + last_idx = cur_idx; + } + } + else if (clear_unbuffered) + memset(&tracks[cur_idx], 0, sizeof(struct track_info)); + } + + /* We clear the previous instance of a buffered track throughout + * the above loop to facilitate 'last' detection. Clear/notify + * the last track here */ + if (last_idx >= 0) + { + if (track_unbuffer_callback) + track_unbuffer_callback(&tracks[last_idx].id3, true); + memset(&tracks[last_idx], 0, sizeof(struct track_info)); + } +} + +/* FIXME: This code should be made more generic and move to metadata.c */ +static void audio_strip_id3v1_tag(void) +{ + int i; + static const unsigned char tag[] = "TAG"; + size_t tag_idx; + size_t cur_idx; + + tag_idx = buf_widx; + if (tag_idx < 128) + tag_idx += filebuflen; + tag_idx -= 128; + + if (filebufused > 128 && tag_idx > buf_ridx) + { + cur_idx = tag_idx; + for(i = 0;i < 3;i++) + { + if(filebuf[cur_idx] != tag[i]) + return; + + if(++cur_idx >= filebuflen) + cur_idx -= filebuflen; + } + + /* Skip id3v1 tag */ + logf("Skipping ID3v1 tag"); + buf_widx = tag_idx; + tracks[track_widx].available -= 128; + tracks[track_widx].filesize -= 128; + filebufused -= 128; + } +} + +static void audio_read_file(bool quick) +{ + size_t copy_n; + int rc; + + /* If we're called and no file is open, this is an error */ + if (current_fd < 0) + { + logf("Bad fd in arf"); + /* Stop this buffer cycle immediately */ + fill_bytesleft = 0; + /* Give some hope of miraculous recovery by forcing a track reload */ + tracks[track_widx].filesize = 0; + return ; + } + + while (tracks[track_widx].filerem > 0) + { + int overlap; + + if (fill_bytesleft == 0) + break ; + + /* copy_n is the largest chunk that is safe to read */ + copy_n = MIN(conf_filechunk, filebuflen - buf_widx); + copy_n = MIN(copy_n, fill_bytesleft); + + /* rc is the actual amount read */ + rc = read(current_fd, &filebuf[buf_widx], copy_n); + + if (rc <= 0) + { + /* Reached the end of the file */ + tracks[track_widx].filerem = 0; + break ; + } + + buf_widx += rc; + + overlap = buf_widx - tracks[track_ridx].buf_idx; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; + if (overlap > 0 && (unsigned) overlap >= filebuflen) + overlap -= filebuflen; + + if (overlap > 0 && overlap <= rc && tracks[track_ridx].available != 0) { + tracks[track_ridx].buf_idx = buf_widx; + tracks[track_ridx].start_pos += overlap; + } + + tracks[track_widx].available += rc; + tracks[track_widx].filerem -= rc; + + filebufused += rc; + if (fill_bytesleft > (unsigned)rc) + fill_bytesleft -= rc; + else + fill_bytesleft = 0; + + /* Let the codec process until it is out of the danger zone, or there + * is an event to handle. In the latter case, break this fill cycle + * immediately */ + if (quick || audio_yield_codecs()) + break; + } + + if (tracks[track_widx].filerem == 0) + { + logf("Finished buf:%dB", tracks[track_widx].filesize); + close(current_fd); + current_fd = -1; + audio_strip_id3v1_tag(); + + track_widx++; + track_widx &= MAX_TRACK_MASK; + + tracks[track_widx].filesize = 0; + } + else + { + logf("Partially buf:%dB", + tracks[track_widx].filesize - tracks[track_widx].filerem); + } +} + +static bool audio_loadcodec(bool start_play) +{ + size_t size; + int fd; + int rc; + size_t copy_n; + int prev_track; + char codec_path[MAX_PATH]; /* Full path to codec */ + + const char * codec_fn = get_codec_filename(tracks[track_widx].id3.codectype); + if (codec_fn == NULL) + return false; + + tracks[track_widx].has_codec = false; + tracks[track_widx].codecsize = 0; + + if (start_play) + { + /* Load the codec directly from disk and save some memory. */ + track_ridx = track_widx; + cur_ti = &tracks[track_ridx]; + ci.filesize = cur_ti->filesize; + ci.id3 = &cur_ti->id3; + ci.taginfo_ready = &cur_ti->taginfo_ready; + ci.curpos = 0; + playing = true; + LOGFQUEUE("codec > codec Q_CODEC_LOAD_DISK"); + queue_post(&codec_queue, Q_CODEC_LOAD_DISK, (void *)codec_fn); + return true; + } + else + { + /* If we already have another track than this one buffered */ + if (track_widx != track_ridx) + { + prev_track = (track_widx - 1) & MAX_TRACK_MASK; + + /* If the previous codec is the same as this one, there is no need + * to put another copy of it on the file buffer */ + if (get_codec_base_type(tracks[track_widx].id3.codectype) == + get_codec_base_type(tracks[prev_track].id3.codectype) + && audio_codec_loaded) + { + logf("Reusing prev. codec"); + return true; + } + } + } + + codec_get_full_path(codec_path, codec_fn); + + fd = open(codec_path, O_RDONLY); + if (fd < 0) + { + logf("Codec doesn't exist!"); + return false; + } + + size = filesize(fd); + + /* Never load a partial codec */ + if (fill_bytesleft < size) + { + logf("Not enough space"); + fill_bytesleft = 0; + close(fd); + return false; + } + + while (tracks[track_widx].codecsize < size) + { + copy_n = MIN(conf_filechunk, filebuflen - buf_widx); + rc = read(fd, &filebuf[buf_widx], copy_n); + if (rc < 0) + return false; + + filebufused += rc; + if (fill_bytesleft > (unsigned)rc) + fill_bytesleft -= rc; + else + fill_bytesleft = 0; + + buf_widx += rc; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; + + tracks[track_widx].codecsize += rc; + + audio_yield_codecs(); + } + + tracks[track_widx].has_codec = true; + + close(fd); + logf("Done: %dB", size); + + return true; +} + +/* TODO: Copied from mpeg.c. Should be moved somewhere else. */ +static void audio_set_elapsed(struct mp3entry* id3) +{ + if ( id3->vbr ) { + if ( id3->has_toc ) { + /* calculate elapsed time using TOC */ + int i; + unsigned int remainder, plen, relpos, nextpos; + + /* find wich percent we're at */ + for (i=0; i<100; i++ ) + if ( id3->offset < id3->toc[i] * (id3->filesize / 256) ) + break; + + i--; + if (i < 0) + i = 0; + + relpos = id3->toc[i]; + + if (i < 99) + nextpos = id3->toc[i+1]; + else + nextpos = 256; + + remainder = id3->offset - (relpos * (id3->filesize / 256)); + + /* set time for this percent (divide before multiply to prevent + overflow on long files. loss of precision is negligible on + short files) */ + id3->elapsed = i * (id3->length / 100); + + /* calculate remainder time */ + plen = (nextpos - relpos) * (id3->filesize / 256); + id3->elapsed += (((remainder * 100) / plen) * + (id3->length / 10000)); + } + else { + /* no TOC exists. set a rough estimate using average bitrate */ + int tpk = id3->length / (id3->filesize / 1024); + id3->elapsed = id3->offset / 1024 * tpk; + } + } + else + { + /* constant bitrate, use exact calculation */ + if (id3->bitrate != 0) + id3->elapsed = id3->offset / (id3->bitrate / 8); + } +} + +static bool audio_load_track(int offset, bool start_play, bool rebuffer) +{ + char *trackname; + off_t size; + char msgbuf[80]; + + /* Stop buffer filling if there is no free track entries. + Don't fill up the last track entry (we wan't to store next track + metadata there). */ + if (!audio_have_free_tracks()) + { + logf("No free tracks"); + return false; + } + + if (current_fd >= 0) + { + logf("Nonzero fd in alt"); + close(current_fd); + current_fd = -1; + } + + last_peek_offset++; + peek_again: + logf("Buffering track:%d/%d", track_widx, track_ridx); + /* Get track name from current playlist read position. */ + while ((trackname = playlist_peek(last_peek_offset)) != NULL) + { + /* Handle broken playlists. */ + current_fd = open(trackname, O_RDONLY); + if (current_fd < 0) + { + logf("Open failed"); + /* Skip invalid entry from playlist. */ + playlist_skip_entry(NULL, last_peek_offset); + } + else + break; + } + + if (!trackname) + { + logf("End-of-playlist"); + playlist_end = true; + return false; + } + + /* Initialize track entry. */ + size = filesize(current_fd); + tracks[track_widx].filerem = size; + tracks[track_widx].filesize = size; + tracks[track_widx].available = 0; + + /* Set default values */ + if (start_play) + { + int last_codec = current_codec; + + current_codec = CODEC_IDX_AUDIO; + conf_watermark = AUDIO_DEFAULT_WATERMARK; + conf_filechunk = AUDIO_DEFAULT_FILECHUNK; + conf_preseek = AUDIO_REBUFFER_GUESS_SIZE; + dsp_configure(DSP_RESET, 0); + current_codec = last_codec; + } + + /* Get track metadata if we don't already have it. */ + if (!tracks[track_widx].taginfo_ready) + { + if (get_metadata(&tracks[track_widx],current_fd,trackname,v1first)) + { + if (start_play) + { + track_changed = true; + playlist_update_resume_info(audio_current_track()); + } + } + else + { + logf("mde:%s!",trackname); + + /* Set filesize to zero to indicate no file was loaded. */ + tracks[track_widx].filesize = 0; + tracks[track_widx].filerem = 0; + close(current_fd); + current_fd = -1; + + /* Skip invalid entry from playlist. */ + playlist_skip_entry(NULL, last_peek_offset); + tracks[track_widx].taginfo_ready = false; + goto peek_again; + } + + } + + /* Load the codec. */ + tracks[track_widx].codecbuf = &filebuf[buf_widx]; + if (!audio_loadcodec(start_play)) + { + if (tracks[track_widx].codecsize) + { + /* Must undo the buffer write of the partial codec */ + logf("Partial codec loaded"); + fill_bytesleft += tracks[track_widx].codecsize; + filebufused -= tracks[track_widx].codecsize; + if (buf_widx < tracks[track_widx].codecsize) + buf_widx += filebuflen; + buf_widx -= tracks[track_widx].codecsize; + tracks[track_widx].codecsize = 0; + } + + /* Set filesize to zero to indicate no file was loaded. */ + tracks[track_widx].filesize = 0; + tracks[track_widx].filerem = 0; + close(current_fd); + current_fd = -1; + + /* Try skipping to next track if there is space. */ + if (fill_bytesleft > 0) + { + /* This is an error condition unless the fill_bytesleft is 0 */ + snprintf(msgbuf, sizeof(msgbuf)-1, "No codec for: %s", trackname); + /* We should not use gui_syncplash from audio thread! */ + gui_syncsplash(HZ*2, true, msgbuf); + /* Skip invalid entry from playlist. */ + playlist_skip_entry(NULL, last_peek_offset); + tracks[track_widx].taginfo_ready = false; + goto peek_again; + } + + return false; + } + + tracks[track_widx].start_pos = 0; + set_filebuf_watermark(buffer_margin); + tracks[track_widx].id3.elapsed = 0; + + if (offset > 0) + { + switch (tracks[track_widx].id3.codectype) { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + lseek(current_fd, offset, SEEK_SET); + tracks[track_widx].id3.offset = offset; + audio_set_elapsed(&tracks[track_widx].id3); + tracks[track_widx].filerem = size - offset; + ci.curpos = offset; + tracks[track_widx].start_pos = offset; + break; + + case AFMT_WAVPACK: + lseek(current_fd, offset, SEEK_SET); + tracks[track_widx].id3.offset = offset; + tracks[track_widx].id3.elapsed = + tracks[track_widx].id3.length / 2; + tracks[track_widx].filerem = size - offset; + ci.curpos = offset; + tracks[track_widx].start_pos = offset; + break; + + case AFMT_OGG_VORBIS: + case AFMT_FLAC: + case AFMT_PCM_WAV: + case AFMT_A52: + case AFMT_AAC: + tracks[track_widx].id3.offset = offset; + break; + } + } + + logf("alt:%s", trackname); + tracks[track_widx].buf_idx = buf_widx; + + audio_read_file(rebuffer); + + return true; } -static void audio_reset_buffer(void) +static bool audio_read_next_metadata(void) { - size_t offset; - - /* Set up file buffer as all space available */ - filebuf = (char *)&audiobuf[talk_get_bufsize()+MALLOC_BUFSIZE]; - filebuflen = audiobufend - (unsigned char *) filebuf - GUARD_BUFSIZE - - (pcmbuf_get_bufsize() + get_pcmbuf_descsize() + PCMBUF_MIX_CHUNK * 2); + int fd; + char *trackname; + int next_idx; + int status; - /* Allow for codec(s) at end of file buffer */ - if (talk_voice_required()) + next_idx = track_widx; + if (tracks[next_idx].taginfo_ready) { - /* Allow 2 codecs at end of file buffer */ - filebuflen -= 2 * (CODEC_IRAM_SIZE + CODEC_SIZE); + next_idx++; + next_idx &= MAX_TRACK_MASK; -#ifndef SIMULATOR - iram_buf[0] = &filebuf[filebuflen]; - iram_buf[1] = &filebuf[filebuflen+CODEC_IRAM_SIZE]; -#endif - dram_buf[0] = (unsigned char *)&filebuf[filebuflen+CODEC_IRAM_SIZE*2]; - dram_buf[1] = (unsigned char *)&filebuf[filebuflen+CODEC_IRAM_SIZE*2+CODEC_SIZE]; + if (tracks[next_idx].taginfo_ready) + return true; } - else - { - /* Allow for 1 codec at end of file buffer */ - filebuflen -= CODEC_IRAM_SIZE + CODEC_SIZE; -#ifndef SIMULATOR - iram_buf[0] = &filebuf[filebuflen]; - iram_buf[1] = NULL; -#endif - dram_buf[0] = (unsigned char *)&filebuf[filebuflen+CODEC_IRAM_SIZE]; - dram_buf[1] = NULL; + trackname = playlist_peek(last_peek_offset + 1); + if (!trackname) + return false; + + fd = open(trackname, O_RDONLY); + if (fd < 0) + return false; + + status = get_metadata(&tracks[next_idx],fd,trackname,v1first); + /* Preload the glyphs in the tags */ + if (status) + { + if (tracks[next_idx].id3.title) + lcd_getstringsize(tracks[next_idx].id3.title, NULL, NULL); + if (tracks[next_idx].id3.artist) + lcd_getstringsize(tracks[next_idx].id3.artist, NULL, NULL); + if (tracks[next_idx].id3.album) + lcd_getstringsize(tracks[next_idx].id3.album, NULL, NULL); } + close(fd); - /* Ensure that file buffer is aligned */ - offset = (-(size_t)filebuf) & 3; - filebuf += offset; - filebuflen -= offset; - filebuflen &= ~3; + return status; } -void audio_load_encoder(int enc_id) +/* Send callback events to notify about new tracks. */ +static void audio_generate_postbuffer_events(void) { -#if defined(HAVE_RECORDING) && !defined(SIMULATOR) - const char *enc_fn = get_codec_filename(enc_id | CODEC_TYPE_ENCODER); - if (!enc_fn) - return; + int cur_idx; + int last_idx = -1; - audio_remove_encoder(); + logf("Postbuffer:%d/%d",track_ridx,track_widx); - LOGFQUEUE("audio > codec Q_ENCODER_LOAD_DISK"); - queue_post(&codec_queue, Q_ENCODER_LOAD_DISK, (void *)enc_fn); + if (audio_have_tracks()) + { + cur_idx = track_ridx; + + while (1) { + if (!tracks[cur_idx].event_sent) + { + if (last_idx >= 0 && !tracks[last_idx].event_sent) + { + /* Mark the event 'sent' even if we don't really send one */ + tracks[last_idx].event_sent = true; + if (track_buffer_callback) + track_buffer_callback(&tracks[last_idx].id3, false); + } + last_idx = cur_idx; + } + if (cur_idx == track_widx) + break; + cur_idx++; + cur_idx &= MAX_TRACK_MASK; + } - while (!ci.enc_codec_loaded) - yield(); -#endif - return; - (void)enc_id; -} /* audio_load_encoder */ + if (last_idx >= 0 && !tracks[last_idx].event_sent) + { + tracks[last_idx].event_sent = true; + if (track_buffer_callback) + track_buffer_callback(&tracks[last_idx].id3, true); + } + + /* Force WPS reload. */ + track_changed = true; + } +} -void audio_remove_encoder(void) +static bool audio_initialize_buffer_fill(bool clear_tracks) { -#if defined(HAVE_RECORDING) && !defined(SIMULATOR) - /* force encoder codec unload (if previously loaded) */ - if (!ci.enc_codec_loaded) - return; + /* Don't initialize if we're already initialized */ + if (filling) + return true; - ci.stop_codec = true; - while (ci.enc_codec_loaded) - yield(); -#endif -} /* audio_remove_encoder */ + /* Don't start buffer fill if buffer is already full. */ + if (filebufused > conf_watermark && !filling) + return false; + + logf("Starting buffer fill"); + + fill_bytesleft = filebuflen - filebufused; + /* TODO: This doesn't look right, and might explain some problems with + * seeking in large files (to offsets larger than filebuflen). + * And what about buffer wraps? + * + * This really doesn't look right, so don't use it. + */ + // if (buf_ridx > cur_ti->buf_idx) + // cur_ti->start_pos = buf_ridx - cur_ti->buf_idx; + + /* Set the filling flag true before calling audio_clear_tracks as that + * function can yield and we start looping. */ + filling = true; + + if (clear_tracks) + audio_clear_track_entries(true, false, true); + + /* Save the current resume position once. */ + playlist_update_resume_info(audio_current_track()); + + return true; +} -static void voice_codec_thread(void) +static void audio_fill_file_buffer( + bool start_play, bool rebuffer, size_t offset) { - while (1) + bool had_next_track = audio_next_track() != NULL; + + if (!audio_initialize_buffer_fill(!start_play)) + return ; + + /* If we have a partially buffered track, continue loading, + * otherwise load a new track */ + if (tracks[track_widx].filesize > 0) + audio_read_file(false); + else if (!audio_load_track(offset, start_play, rebuffer)) + fill_bytesleft = 0; + + if (!had_next_track && audio_next_track()) + track_changed = true; + + /* If we're done buffering */ + if (fill_bytesleft == 0) { - logf("Loading voice codec"); - voice_codec_loaded = true; - mutex_lock(&mutex_codecthread); - current_codec = CODEC_IDX_VOICE; - dsp_configure(DSP_RESET, 0); - voice_remaining = 0; - voice_getmore = NULL; + audio_read_next_metadata(); - codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice); + audio_generate_postbuffer_events(); + filling = false; - logf("Voice codec finished"); - voice_codec_loaded = false; - mutex_unlock(&mutex_codecthread); +#ifndef SIMULATOR + if (playing) + ata_sleep(); +#endif } -} /* voice_codec_thread */ +} -void voice_init(void) +static void audio_rebuffer(void) { - if (!filebuf) - return; /* Audio buffers not yet set up */ - - if (voice_thread_num >= 0) + logf("Forcing rebuffer"); + + /* Notify the codec that this will take a while */ + /* Currently this can cause some problems (logf in reverse order): + * Codec load error:-1 + * Codec load disk + * Codec: Unsupported + * Codec finished + * New codec:0/3 + * Clearing tracks:7/7, 1 + * Forcing rebuffer + * Check new track buffer + * Request new track + * Clearing tracks:5/5, 0 + * Starting buffer fill + * Clearing tracks:5/5, 1 + * Re-buffering song w/seek + */ + //if (!filling) + // queue_post(&codec_callback_queue, Q_CODEC_REQUEST_PENDING, 0); + + /* Stop in progress fill, and clear open file descriptor */ + if (current_fd >= 0) { - logf("Terminating voice codec"); - remove_thread(voice_thread_num); - if (current_codec == CODEC_IDX_VOICE) - mutex_unlock(&mutex_codecthread); - queue_delete(&voice_codec_queue); - voice_thread_num = -1; - voice_codec_loaded = false; + close(current_fd); + current_fd = -1; } + filling = false; - if (!talk_voice_required()) - return; - - logf("Starting voice codec"); - queue_init(&voice_codec_queue); - voice_thread_num = create_thread(voice_codec_thread, voice_codec_stack, - sizeof(voice_codec_stack), voice_codec_thread_name); - - while (!voice_codec_loaded) - yield(); -} /* voice_init */ + /* Reset buffer and track pointers */ + tracks[track_ridx].buf_idx = buf_ridx = buf_widx = 0; + track_widx = track_ridx; + cur_ti = &tracks[track_ridx]; + audio_clear_track_entries(true, true, false); + filebufused = 0; + cur_ti->available = 0; -void voice_stop(void) -{ - /* Messages should not be posted to voice codec queue unless it is the - current codec or deadlocks happen. This will be addressed globally soon. - -- jhMikeS */ - if (current_codec != CODEC_IDX_VOICE) - return; + /* Fill the buffer */ + last_peek_offset = -1; + cur_ti->filesize = 0; + cur_ti->start_pos = 0; + ci.curpos = 0; - mp3_play_stop(); - while (voice_is_playing && !queue_empty(&voice_codec_queue)) - yield(); -} /* voice_stop */ + if (!cur_ti->taginfo_ready) + memset(&cur_ti->id3, 0, sizeof(struct mp3entry)); -struct mp3entry* audio_current_track(void) + audio_fill_file_buffer(false, true, 0); +} + +static void audio_check_new_track(void) { - const char *filename; - const char *p; - static struct mp3entry temp_id3; - int cur_idx; - - cur_idx = track_ridx + ci.new_track; - cur_idx &= MAX_TRACK_MASK; + int track_count = audio_track_count(); + int old_track_ridx = track_ridx; + bool forward; - if (tracks[cur_idx].taginfo_ready) - return &tracks[cur_idx].id3; + if (dir_skip) + { + dir_skip = false; + if (playlist_next_dir(ci.new_track)) + { + ci.new_track = 0; + cur_ti->taginfo_ready = false; + audio_rebuffer(); + goto skip_done; + } + else + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } + } - memset(&temp_id3, 0, sizeof(struct mp3entry)); - - filename = playlist_peek(ci.new_track); - if (!filename) - filename = "No file!"; + if (new_playlist) + ci.new_track = 0; -#ifdef HAVE_TC_RAMCACHE - if (tagcache_fill_tags(&temp_id3, filename)) - return &temp_id3; -#endif + /* If the playlist isn't that big */ + if (!playlist_check(ci.new_track)) + { + if (ci.new_track >= 0) + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } + /* Find the beginning backward if the user over-skips it */ + while (!playlist_check(++ci.new_track)) + if (ci.new_track >= 0) + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } + } + /* Update the playlist */ + last_peek_offset -= ci.new_track; - p = strrchr(filename, '/'); - if (!p) - p = filename; - else - p++; + if (playlist_next(ci.new_track) < 0) + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } - strncpy(temp_id3.path, p, sizeof(temp_id3.path)-1); - temp_id3.title = &temp_id3.path[0]; + if (new_playlist) + { + ci.new_track = 1; + new_playlist = false; + } - return &temp_id3; -} + track_ridx += ci.new_track; + track_ridx &= MAX_TRACK_MASK; -struct mp3entry* audio_next_track(void) -{ - int next_idx = track_ridx; + /* Save the old track */ + prev_ti = cur_ti; + /* Move to the new track */ + cur_ti = &tracks[track_ridx]; - if (!have_tracks()) - return NULL; + if (automatic_skip) + playlist_end = false; - next_idx++; - next_idx &= MAX_TRACK_MASK; + track_changed = !automatic_skip; - if (!tracks[next_idx].taginfo_ready) - return NULL; + /* If it is not safe to even skip this many track entries */ + if (ci.new_track >= track_count || ci.new_track <= track_count - MAX_TRACK) + { + ci.new_track = 0; + cur_ti->taginfo_ready = false; + audio_rebuffer(); + goto skip_done; + } - return &tracks[next_idx].id3; -} + forward = ci.new_track > 0; + ci.new_track = 0; -bool audio_has_changed_track(void) -{ - if (track_changed) + /* If the target track is clearly not in memory */ + if (cur_ti->filesize == 0 || !cur_ti->taginfo_ready) { - track_changed = false; - return true; + audio_rebuffer(); + goto skip_done; } - return false; -} - -void audio_play(long offset) -{ - logf("audio_play"); - if (playing && offset <= 0) + /* The track may be in memory, see if it really is */ + if (forward) { - LOGFQUEUE("audio > audio Q_AUDIO_NEW_PLAYLIST"); - queue_post(&audio_queue, Q_AUDIO_NEW_PLAYLIST, 0); + if (!audio_buffer_wind_forward(track_ridx, old_track_ridx)) + audio_rebuffer(); } else { - if (playing) - audio_stop(); + int cur_idx = track_ridx; + bool taginfo_ready = true; + bool wrap = track_ridx > old_track_ridx; + + while (1) + { + cur_idx++; + cur_idx &= MAX_TRACK_MASK; + if (!(wrap || cur_idx < old_track_ridx)) + break; - playing = true; - LOGFQUEUE("audio > audio Q_AUDIO_PLAY"); - queue_post(&audio_queue, Q_AUDIO_PLAY, (void *)offset); + /* If we hit a track in between without valid tag info, bail */ + if (!tracks[cur_idx].taginfo_ready) + { + taginfo_ready = false; + break; + } + + tracks[cur_idx].available = tracks[cur_idx].filesize; + if (tracks[cur_idx].codecsize) + tracks[cur_idx].has_codec = true; + } + if (taginfo_ready) + { + if (!audio_buffer_wind_backward(track_ridx, old_track_ridx)) + audio_rebuffer(); + } + else + { + cur_ti->taginfo_ready = false; + audio_rebuffer(); + } } -} -void audio_stop(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_STOP"); - queue_post(&audio_queue, Q_AUDIO_STOP, 0); - while (playing || audio_codec_loaded) - yield(); +skip_done: + audio_update_trackinfo(); + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); } -bool mp3_pause_done(void) +static void audio_rebuffer_and_seek(size_t newpos) { - return pcm_is_paused(); -} + int fd; + char *trackname; -void audio_pause(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_PAUSE"); - queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)true); -} + trackname = playlist_peek(0); + /* (Re-)open current track's file handle. */ -void audio_resume(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_PAUSE resume"); - queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)false); -} + fd = open(trackname, O_RDONLY); + if (fd < 0) + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } + + if (current_fd >= 0) + close(current_fd); + current_fd = fd; -void audio_next(void) -{ - if (global_settings.beep) - pcmbuf_beep(5000, 100, 2500*global_settings.beep); + playlist_end = false; + + ci.curpos = newpos; + + /* Clear codec buffer. */ + track_widx = track_ridx; + filebufused = 0; + tracks[track_widx].buf_idx = buf_widx = buf_ridx = 0; + + last_peek_offset = 0; + filling = false; + audio_initialize_buffer_fill(true); + filling = true; + + if (newpos > conf_preseek) { + buf_ridx += conf_preseek; + cur_ti->start_pos = newpos - conf_preseek; + } + else + { + buf_ridx += newpos; + cur_ti->start_pos = 0; + } - /* Should be safe to do outside of thread, that way we get - * the instant wps response at least. */ - audio_initiate_track_change(1); - // queue_post(&audio_queue, Q_AUDIO_SKIP, (void *)1); -} + cur_ti->filerem = cur_ti->filesize - cur_ti->start_pos; + cur_ti->available = 0; -void audio_prev(void) -{ - if (global_settings.beep) - pcmbuf_beep(5000, 100, 2500*global_settings.beep); + lseek(current_fd, cur_ti->start_pos, SEEK_SET); - audio_initiate_track_change(-1); - // queue_post(&audio_queue, Q_AUDIO_SKIP, (void *)-1); + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); } -void audio_next_dir(void) +void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3, + bool last_track)) { - LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP 1"); - queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)1); + track_buffer_callback = handler; } -void audio_prev_dir(void) +void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3, + bool last_track)) { - LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP -1"); - queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)-1); + track_unbuffer_callback = handler; } -void audio_pre_ff_rewind(void) +void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3)) { - LOGFQUEUE("audio > audio Q_AUDIO_PRE_FF_REWIND"); - queue_post(&audio_queue, Q_AUDIO_PRE_FF_REWIND, 0); + track_changed_callback = handler; } -void audio_ff_rewind(long newpos) +static void audio_stop_codec_flush(void) { - LOGFQUEUE("audio > audio Q_AUDIO_FF_REWIND"); - queue_post(&audio_queue, Q_AUDIO_FF_REWIND, (int *)newpos); + ci.stop_codec = true; + pcmbuf_pause(true); + while (audio_codec_loaded) + yield(); + /* If the audio codec is not loaded any more, and the audio is still + * playing, it is now and _only_ now safe to call this function from the + * audio thread */ + if (pcm_is_playing()) + pcmbuf_play_stop(); + pcmbuf_pause(paused); } -void audio_flush_and_reload_tracks(void) +static void audio_stop_playback(void) { - LOGFQUEUE("audio > audio Q_AUDIO_FLUSH"); - queue_post(&audio_queue, Q_AUDIO_FLUSH, 0); -} + /* If we were playing, save resume information */ + if (playing) + { + /* Save the current playing spot, or NULL if the playlist has ended */ + playlist_update_resume_info( + (playlist_end && ci.stop_codec)?NULL:audio_current_track()); + } -void audio_error_clear(void) -{ + while (voice_is_playing && !queue_empty(&voice_queue)) + yield(); + + filebufused = 0; + playing = false; + filling = false; + paused = false; + audio_stop_codec_flush(); + + if (current_fd >= 0) + { + close(current_fd); + current_fd = -1; + } + + /* Mark all entries null. */ + audio_clear_track_entries(true, false, false); + memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); } -int audio_status(void) +static void audio_play_start(size_t offset) { - int ret = 0; +#if defined(HAVE_RECORDING) || defined(CONFIG_TUNER) + rec_set_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); +#endif - if (playing) - ret |= AUDIO_STATUS_PLAY; + /* Wait for any previously playing audio to flush - TODO: Not necessary? */ + while (audio_codec_loaded) + audio_stop_codec_flush(); - if (paused) - ret |= AUDIO_STATUS_PAUSE; + track_changed = true; + playlist_end = false; -#ifdef HAVE_RECORDING - /* Do this here for constitency with mpeg.c version */ - ret |= pcm_rec_status(); -#endif + playing = true; + ci.new_track = 0; + ci.seek_time = 0; - return ret; -} + if (current_fd >= 0) + { + close(current_fd); + current_fd = -1; + } -bool audio_query_poweroff(void) -{ - return !(playing && paused); -} + sound_set_volume(global_settings.volume); + track_widx = track_ridx = 0; + buf_ridx = buf_widx = 0; + filebufused = 0; -int audio_get_file_pos(void) -{ - return 0; + /* Mark all entries null. */ + memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); + + last_peek_offset = -1; + + audio_fill_file_buffer(true, false, offset); } -/* TODO: Copied from mpeg.c. Should be moved somewhere else. */ -static void mp3_set_elapsed(struct mp3entry* id3) +/* Invalidates all but currently playing track. */ +void audio_invalidate_tracks(void) { - if ( id3->vbr ) { - if ( id3->has_toc ) { - /* calculate elapsed time using TOC */ - int i; - unsigned int remainder, plen, relpos, nextpos; - - /* find wich percent we're at */ - for (i=0; i<100; i++ ) - if ( id3->offset < id3->toc[i] * (id3->filesize / 256) ) - break; - - i--; - if (i < 0) - i = 0; - - relpos = id3->toc[i]; + if (audio_have_tracks()) { + last_peek_offset = 0; - if (i < 99) - nextpos = id3->toc[i+1]; - else - nextpos = 256; + playlist_end = false; + track_widx = track_ridx; + audio_clear_track_entries(true, true, true); - remainder = id3->offset - (relpos * (id3->filesize / 256)); + /* If the current track is fully buffered, advance the write pointer */ + if (tracks[track_widx].filerem == 0) + track_widx = (track_widx + 1) & MAX_TRACK_MASK; - /* set time for this percent (divide before multiply to prevent - overflow on long files. loss of precision is negligible on - short files) */ - id3->elapsed = i * (id3->length / 100); + /* Mark all other entries null (also buffered wrong metadata). */ + filebufused = cur_ti->available; + buf_widx = buf_ridx + cur_ti->available; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; - /* calculate remainder time */ - plen = (nextpos - relpos) * (id3->filesize / 256); - id3->elapsed += (((remainder * 100) / plen) * - (id3->length / 10000)); - } - else { - /* no TOC exists. set a rough estimate using average bitrate */ - int tpk = id3->length / (id3->filesize / 1024); - id3->elapsed = id3->offset / 1024 * tpk; - } - } - else - { - /* constant bitrate, use exact calculation */ - if (id3->bitrate != 0) - id3->elapsed = id3->offset / (id3->bitrate / 8); + audio_read_next_metadata(); } } -/* Copied from mpeg.c. Should be moved somewhere else. */ -static int mp3_get_file_pos(void) +static void audio_new_playlist(void) { - int pos = -1; - struct mp3entry *id3 = audio_current_track(); - - if (id3->vbr) - { - if (id3->has_toc) - { - /* Use the TOC to find the new position */ - unsigned int percent, remainder; - int curtoc, nexttoc, plen; - - percent = (id3->elapsed*100)/id3->length; - if (percent > 99) - percent = 99; - - curtoc = id3->toc[percent]; - - if (percent < 99) - nexttoc = id3->toc[percent+1]; - else - nexttoc = 256; - - pos = (id3->filesize/256)*curtoc; - - /* Use the remainder to get a more accurate position */ - remainder = (id3->elapsed*100)%id3->length; - remainder = (remainder*100)/id3->length; - plen = (nexttoc - curtoc)*(id3->filesize/256); - pos += (plen/100)*remainder; - } - else - { - /* No TOC exists, estimate the new position */ - pos = (id3->filesize / (id3->length / 1000)) * - (id3->elapsed / 1000); - } - } - else if (id3->bitrate) - pos = id3->elapsed * (id3->bitrate / 8); - else - return -1; + /* Prepare to start a new fill from the beginning of the playlist */ + last_peek_offset = -1; + if (audio_have_tracks()) { + playlist_end = false; + track_widx = track_ridx; + audio_clear_track_entries(true, true, true); - /* Don't seek right to the end of the file so that we can - transition properly to the next song */ - if (pos >= (int)(id3->filesize - id3->id3v1len)) - pos = id3->filesize - id3->id3v1len - 1; - /* skip past id3v2 tag and other leading garbage */ - else if (pos < (int)id3->first_frame_offset) - pos = id3->first_frame_offset; + track_widx++; + track_widx &= MAX_TRACK_MASK; - return pos; -} + /* Stop reading the current track */ + cur_ti->filerem = 0; + close(current_fd); + current_fd = -1; -void mp3_play_data(const unsigned char* start, int size, - void (*get_more)(unsigned char** start, int* size)) -{ - static struct voice_info voice_clip; - voice_clip.callback = get_more; - voice_clip.buf = (char *)start; - voice_clip.size = size; - logf("mp3 > voice Q_VOICE_STOP"); - queue_post(&voice_codec_queue, Q_VOICE_STOP, 0); - logf("mp3 > voice Q_VOICE_PLAY"); - queue_post(&voice_codec_queue, Q_VOICE_PLAY, &voice_clip); - voice_is_playing = true; - voice_boost_cpu(true); + /* Mark the current track as invalid to prevent skipping back to it */ + cur_ti->taginfo_ready = false; + + /* Invalidate the buffer other than the playing track */ + filebufused = cur_ti->available; + buf_widx = buf_ridx + cur_ti->available; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; + } + + /* Signal the codec to initiate a track change forward */ + new_playlist = true; + ci.new_track = 1; + audio_fill_file_buffer(false, true, 0); } -void mp3_play_stop(void) +static void audio_initiate_track_change(long direction) { - logf("mp3 > voice Q_VOICE_STOP"); - queue_post(&voice_codec_queue, Q_VOICE_STOP, 0); + if (playlist_check(direction)) + { + playlist_end = false; + /* Flag track changed immediately so wps can update instantly. + * No need to wait for disk to spin up or message to travel + * through the deep queues as this info is only for the wps. */ + track_changed = true; + ci.new_track += direction; + } } -void audio_set_buffer_margin(int setting) +static void audio_initiate_dir_change(long direction) { - static const int lookup[] = {5, 15, 30, 60, 120, 180, 300, 600}; - buffer_margin = lookup[setting]; - logf("buffer margin: %ds", buffer_margin); - set_filebuf_watermark(buffer_margin); + playlist_end = false; + dir_skip = true; + ci.new_track = direction; } -/* Set crossfade & PCM buffer length. */ -void audio_set_crossfade(int enable) +static void audio_reset_buffer(void) { - size_t size; - bool was_playing = (playing && audio_is_initialized); - size_t offset = 0; -#if MEM > 1 - int seconds = 1; -#endif + size_t offset; - if (!filebuf) - return; /* Audio buffers not yet set up */ + /* Set up file buffer as all space available */ + filebuf = (char *)&audiobuf[talk_get_bufsize()+MALLOC_BUFSIZE]; + filebuflen = audiobufend - (unsigned char *) filebuf - GUARD_BUFSIZE - + (pcmbuf_get_bufsize() + get_pcmbuf_descsize() + PCMBUF_MIX_CHUNK * 2); -#if MEM > 1 - if (enable) - seconds = global_settings.crossfade_fade_out_delay - + global_settings.crossfade_fade_out_duration; + /* Allow for codec(s) at end of file buffer */ + if (talk_voice_required()) + { + /* Allow 2 codecs at end of file buffer */ + filebuflen -= 2 * (CODEC_IRAM_SIZE + CODEC_SIZE); - /* Buffer has to be at least 2s long. */ - seconds += 2; - logf("buf len: %d", seconds); - size = seconds * (NATIVE_FREQUENCY*4); -#else - enable = 0; - size = NATIVE_FREQUENCY*2; +#ifndef SIMULATOR + iram_buf[0] = &filebuf[filebuflen]; + iram_buf[1] = &filebuf[filebuflen+CODEC_IRAM_SIZE]; #endif - if (pcmbuf_get_bufsize() == size) - return ; - - if (was_playing) - { - /* Store the track resume position */ - offset = cur_ti->id3.offset; - /* Playback has to be stopped before changing the buffer size. */ - LOGFQUEUE("audio > audio Q_AUDIO_STOP"); - queue_post(&audio_queue, Q_AUDIO_STOP, 0); - while (audio_codec_loaded) - yield(); - gui_syncsplash(0, true, (char *)str(LANG_RESTARTING_PLAYBACK)); + dram_buf[0] = (unsigned char *)&filebuf[filebuflen+CODEC_IRAM_SIZE*2]; + dram_buf[1] = (unsigned char *)&filebuf[filebuflen+CODEC_IRAM_SIZE*2+CODEC_SIZE]; } + else + { + /* Allow for 1 codec at end of file buffer */ + filebuflen -= CODEC_IRAM_SIZE + CODEC_SIZE; - /* Re-initialize audio system. */ - pcmbuf_init(size); - pcmbuf_crossfade_enable(enable); - audio_reset_buffer(); - logf("abuf:%dB", pcmbuf_get_bufsize()); - logf("fbuf:%dB", filebuflen); - - voice_init(); - - /* Restart playback. */ - if (was_playing) { - playing = true; - LOGFQUEUE("audio > audio Q_AUDIO_PLAY"); - queue_post(&audio_queue, Q_AUDIO_PLAY, (void *)offset); - - /* Wait for the playback to start again (and display the splash - screen during that period. */ - while (playing && !audio_codec_loaded) - yield(); +#ifndef SIMULATOR + iram_buf[0] = &filebuf[filebuflen]; + iram_buf[1] = NULL; +#endif + dram_buf[0] = (unsigned char *)&filebuf[filebuflen+CODEC_IRAM_SIZE]; + dram_buf[1] = NULL; } -} -void mpeg_id3_options(bool _v1first) -{ - v1first = _v1first; + /* Ensure that file buffer is aligned */ + offset = (-(size_t)filebuf) & 3; + filebuf += offset; + filebuflen -= offset; + filebuflen &= ~3; } + #ifdef ROCKBOX_HAS_LOGF -void test_track_changed_event(struct mp3entry *id3) +static void audio_test_track_changed_event(struct mp3entry *id3) { (void)id3; @@ -3182,7 +3122,7 @@ void test_track_changed_event(struct mp3entry *id3) } #endif -static void playback_init(void) +static void audio_playback_init(void) { static bool voicetagtrue = true; struct event ev; @@ -3196,14 +3136,14 @@ static void playback_init(void) #endif #ifdef ROCKBOX_HAS_LOGF - audio_set_track_changed_event(test_track_changed_event); + audio_set_track_changed_event(audio_test_track_changed_event); #endif /* Initialize codec api. */ ci.read_filebuf = codec_filebuf_callback; ci.pcmbuf_insert = codec_pcmbuf_insert_callback; ci.pcmbuf_insert_split = codec_pcmbuf_insert_split_callback; - ci.get_codec_memory = get_codec_memory_callback; + ci.get_codec_memory = codec_get_memory_callback; ci.request_buffer = codec_request_buffer_callback; ci.advance_buffer = codec_advance_buffer_callback; ci.advance_buffer_loc = codec_advance_buffer_loc_callback; @@ -3222,7 +3162,7 @@ static void playback_init(void) ci_voice.read_filebuf = voice_filebuf_callback; ci_voice.pcmbuf_insert = voice_pcmbuf_insert_callback; ci_voice.pcmbuf_insert_split = voice_pcmbuf_insert_split_callback; - ci_voice.get_codec_memory = get_voice_memory_callback; + ci_voice.get_codec_memory = voice_get_memory_callback; ci_voice.request_buffer = voice_request_buffer_callback; ci_voice.advance_buffer = voice_advance_buffer_callback; ci_voice.advance_buffer_loc = voice_advance_buffer_loc_callback; @@ -3266,39 +3206,120 @@ static void playback_init(void) sound_settings_apply(); } -void audio_preinit(void) +static void audio_thread(void) { - logf("playback system pre-init"); + struct event ev; - filebufused = 0; - filling = false; - current_codec = CODEC_IDX_AUDIO; - playing = false; - paused = false; - audio_codec_loaded = false; - voice_is_playing = false; - track_changed = false; - current_fd = -1; - track_buffer_callback = NULL; - track_unbuffer_callback = NULL; - track_changed_callback = NULL; - /* Just to prevent cur_ti never be anything random. */ - cur_ti = &tracks[0]; + /* At first initialize audio system in background. */ + audio_playback_init(); - mutex_init(&mutex_codecthread); + while (1) { + if (filling) + { + queue_wait_w_tmo(&audio_queue, &ev, 0); + if (ev.id == SYS_TIMEOUT) + ev.id = Q_AUDIO_FILL_BUFFER; + } + else + queue_wait_w_tmo(&audio_queue, &ev, HZ); - queue_init(&audio_queue); - queue_init(&codec_queue); - /* clear, not init to create a private queue */ - queue_clear(&codec_callback_queue); + switch (ev.id) { + case Q_AUDIO_FILL_BUFFER: + LOGFQUEUE("audio < Q_AUDIO_FILL_BUFFER"); + if (!filling) + if (!playing || playlist_end || ci.stop_codec) + break; + audio_fill_file_buffer(false, false, 0); + break; - create_thread(audio_thread, audio_stack, sizeof(audio_stack), - audio_thread_name); -} + case Q_AUDIO_PLAY: + LOGFQUEUE("audio < Q_AUDIO_PLAY"); + audio_clear_track_entries(true, false, true); + audio_play_start((size_t)ev.data); + break ; -void audio_init(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_POSTINIT"); - queue_post(&audio_queue, Q_AUDIO_POSTINIT, 0); + case Q_AUDIO_STOP: + LOGFQUEUE("audio < Q_AUDIO_STOP"); + audio_stop_playback(); + break ; + + case Q_AUDIO_PAUSE: + LOGFQUEUE("audio < Q_AUDIO_PAUSE"); + pcmbuf_pause((bool)ev.data); + paused = (bool)ev.data; + break ; + + case Q_AUDIO_SKIP: + LOGFQUEUE("audio < Q_AUDIO_SKIP"); + audio_initiate_track_change((long)ev.data); + break; + + case Q_AUDIO_PRE_FF_REWIND: + LOGFQUEUE("audio < Q_AUDIO_PRE_FF_REWIND"); + if (!playing) + break; + pcmbuf_pause(true); + break; + + case Q_AUDIO_FF_REWIND: + LOGFQUEUE("audio < Q_AUDIO_FF_REWIND"); + if (!playing) + break ; + ci.seek_time = (long)ev.data+1; + break ; + + case Q_AUDIO_REBUFFER_SEEK: + LOGFQUEUE("audio < Q_AUDIO_REBUFFER_SEEK"); + audio_rebuffer_and_seek((size_t)ev.data); + break; + + case Q_AUDIO_CHECK_NEW_TRACK: + LOGFQUEUE("audio < Q_AUDIO_CHECK_NEW_TRACK"); + audio_check_new_track(); + break; + + case Q_AUDIO_DIR_SKIP: + LOGFQUEUE("audio < Q_AUDIO_DIR_SKIP"); + playlist_end = false; + if (global_settings.beep) + pcmbuf_beep(5000, 100, 2500*global_settings.beep); + audio_initiate_dir_change((long)ev.data); + break; + + case Q_AUDIO_NEW_PLAYLIST: + LOGFQUEUE("audio < Q_AUDIO_NEW_PLAYLIST"); + audio_new_playlist(); + break; + + case Q_AUDIO_FLUSH: + LOGFQUEUE("audio < Q_AUDIO_FLUSH"); + audio_invalidate_tracks(); + break ; + + case Q_AUDIO_TRACK_CHANGED: + LOGFQUEUE("audio < Q_AUDIO_TRACK_CHANGED"); + if (track_changed_callback) + track_changed_callback(&cur_ti->id3); + track_changed = true; + playlist_update_resume_info(audio_current_track()); + break ; + +#ifndef SIMULATOR + case SYS_USB_CONNECTED: + LOGFQUEUE("audio < SYS_USB_CONNECTED"); + audio_stop_playback(); + usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_wait_for_disconnect(&audio_queue); + break ; +#endif + + case SYS_TIMEOUT: + LOGFQUEUE("audio < SYS_TIMEOUT"); + break; + + default: + LOGFQUEUE("audio < default"); + } + } } -- cgit v1.2.3