From 3cc46e762065c1d7f6bceebaa39572e30ca78b2b Mon Sep 17 00:00:00 2001 From: Steve Bavin Date: Fri, 1 Sep 2006 07:59:31 +0000 Subject: [PATCH] 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 | 4113 ++++++++++++++++++++++++----------------------- 1 file changed, 2067 insertions(+), 2046 deletions(-) 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,24 +241,452 @@ 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 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); + +/* 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 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 + +static void voice_thread(void); + +/* --- External interfaces --- */ + +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); +} + +void mp3_play_stop(void) +{ + logf("mp3 > voice Q_VOICE_STOP"); + queue_post(&voice_queue, Q_VOICE_STOP, 0); +} + +bool mp3_pause_done(void) +{ + return pcm_is_paused(); +} + +void mpeg_id3_options(bool _v1first) +{ + v1first = _v1first; +} + +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; + + audio_remove_encoder(); + + LOGFQUEUE("audio > codec Q_ENCODER_LOAD_DISK"); + queue_post(&codec_queue, Q_ENCODER_LOAD_DISK, (void *)enc_fn); + + while (!ci.enc_codec_loaded) + yield(); +#endif + return; + (void)enc_id; +} /* audio_load_encoder */ + +void audio_remove_encoder(void) +{ +#if defined(HAVE_RECORDING) && !defined(SIMULATOR) + /* force encoder codec unload (if previously loaded) */ + if (!ci.enc_codec_loaded) + return; + + ci.stop_codec = true; + while (ci.enc_codec_loaded) + yield(); +#endif +} /* audio_remove_encoder */ + +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; + + if (tracks[cur_idx].taginfo_ready) + return &tracks[cur_idx].id3; + + memset(&temp_id3, 0, sizeof(struct mp3entry)); + + filename = playlist_peek(ci.new_track); + if (!filename) + filename = "No file!"; + +#ifdef HAVE_TC_RAMCACHE + if (tagcache_fill_tags(&temp_id3, filename)) + return &temp_id3; +#endif + + p = strrchr(filename, '/'); + if (!p) + p = filename; + else + p++; + + strncpy(temp_id3.path, p, sizeof(temp_id3.path)-1); + temp_id3.title = &temp_id3.path[0]; + + return &temp_id3; +} + +struct mp3entry* audio_next_track(void) +{ + int next_idx = track_ridx; + + if (!audio_have_tracks()) + return NULL; + + next_idx++; + next_idx &= MAX_TRACK_MASK; + + if (!tracks[next_idx].taginfo_ready) + return NULL; + + return &tracks[next_idx].id3; +} + +bool audio_has_changed_track(void) +{ + if (track_changed) + { + track_changed = false; + return true; + } + + return false; +} + +void audio_play(long offset) +{ + 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(); + + playing = true; + LOGFQUEUE("audio > audio Q_AUDIO_PLAY"); + queue_post(&audio_queue, Q_AUDIO_PLAY, (void *)offset); + } +} + +void audio_stop(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_STOP"); + queue_post(&audio_queue, Q_AUDIO_STOP, 0); + while (playing || audio_codec_loaded) + yield(); +} + +void audio_pause(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_PAUSE"); + queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)true); +} + +void audio_resume(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_PAUSE resume"); + queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)false); +} + +void audio_next(void) +{ + 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); +} + +void audio_prev(void) +{ + if (global_settings.beep) + pcmbuf_beep(5000, 100, 2500*global_settings.beep); + + audio_initiate_track_change(-1); + // queue_post(&audio_queue, Q_AUDIO_SKIP, (void *)-1); +} + +void audio_next_dir(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP 1"); + queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)1); +} + +void audio_prev_dir(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP -1"); + queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)-1); +} + +void audio_pre_ff_rewind(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_PRE_FF_REWIND"); + queue_post(&audio_queue, Q_AUDIO_PRE_FF_REWIND, 0); +} + +void audio_ff_rewind(long newpos) +{ + LOGFQUEUE("audio > audio Q_AUDIO_FF_REWIND"); + queue_post(&audio_queue, Q_AUDIO_FF_REWIND, (int *)newpos); +} + +void audio_flush_and_reload_tracks(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_FLUSH"); + queue_post(&audio_queue, Q_AUDIO_FLUSH, 0); +} + +void audio_error_clear(void) +{ +} + +int audio_status(void) +{ + int ret = 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; +} + +bool audio_query_poweroff(void) +{ + return !(playing && paused); +} + +int audio_get_file_pos(void) +{ + return 0; +} + +void audio_set_buffer_margin(int setting) +{ + 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); +} + +/* 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 + + if (!filebuf) + return; /* Audio buffers not yet set up */ + +#if MEM > 1 + if (enable) + seconds = global_settings.crossfade_fade_out_delay + + global_settings.crossfade_fade_out_duration; + + /* 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 ; + + 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)); + } + + /* 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(); + } +} + +void audio_preinit(void) +{ + logf("playback system pre-init"); + + 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]; + + mutex_init(&mutex_codecthread); + + queue_init(&audio_queue); + queue_init(&codec_queue); + /* clear, not init to create a private queue */ + queue_clear(&codec_callback_queue); + + create_thread(audio_thread, audio_stack, sizeof(audio_stack), + audio_thread_name); +} + +void audio_init(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_POSTINIT"); + queue_post(&audio_queue, Q_AUDIO_POSTINIT, 0); +} + +void voice_init(void) +{ + if (!filebuf) + return; /* Audio buffers not yet set up */ + + if (voice_thread_num >= 0) + { + 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; + } + + if (!talk_voice_required()) + return; + + 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 */ + +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; + + mp3_play_stop(); + while (voice_is_playing && !queue_empty(&voice_queue)) + yield(); +} /* voice_stop */ + + + +/* --- Routines called from multiple threads --- */ static void swap_codec(void) { @@ -314,6 +721,43 @@ static void swap_codec(void) logf("resuming codec:%d", my_codec); } +static void set_filebuf_watermark(int seconds) +{ + size_t bytes; + + if (current_codec == CODEC_IDX_VOICE) + return; + + if (!filebuf) + return; /* Audio buffers not yet set up */ + + bytes = MAX(cur_ti->id3.bitrate * seconds * (1000/8), conf_watermark); + bytes = MIN(bytes, filebuflen / 2); + conf_watermark = bytes; +} + +static const char * get_codec_filename(int enc_spec) +{ + const char *fname; + int type = enc_spec & CODEC_TYPE_MASK; + int afmt = enc_spec & CODEC_AFMT_MASK; + + if ((unsigned)afmt >= AFMT_NUM_CODECS) + type = AFMT_UNKNOWN | (type & CODEC_TYPE_MASK); + + fname = (type == CODEC_TYPE_DECODER) ? + audio_formats[afmt].codec_fn : audio_formats[afmt].codec_enc_fn; + + logf("%s: %d - %s", + (type == CODEC_TYPE_ENCODER) ? "Encoder" : "Decoder", + afmt, fname ? fname : ""); + + return fname; +} /* get_codec_filename */ + + +/* --- Voice thread --- */ + #ifdef HAVE_ADJUSTABLE_CPU_FREQ static void voice_boost_cpu(bool state) { @@ -325,8 +769,6 @@ static void voice_boost_cpu(bool state) voice_cpu_boosted = state; } } -#else -#define voice_boost_cpu(state) do { } while(0) #endif static bool voice_pcmbuf_insert_split_callback( @@ -390,6 +832,218 @@ static bool voice_pcmbuf_insert_split_callback( return true; } /* voice_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); +} + +static void* voice_get_memory_callback(size_t *size) +{ + *size = 0; + return NULL; +} + +static void voice_set_elapsed_callback(unsigned int value) +{ + (void)value; +} + +static void voice_set_offset_callback(size_t value) +{ + (void)value; +} + +static size_t voice_filebuf_callback(void *ptr, size_t size) +{ + (void)ptr; + (void)size; + + return 0; +} + +static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize) +{ + struct event ev; + + if (ci_voice.new_track) + { + *realsize = 0; + return NULL; + } + + while (1) + { + if (voice_is_playing) + { + queue_wait_w_tmo(&voice_queue, &ev, 0); + } + else if (playing) + { + queue_wait_w_tmo(&voice_queue, &ev, 0); + if (ev.id == SYS_TIMEOUT) + ev.id = Q_AUDIO_PLAY; + } + else + queue_wait(&voice_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 + + 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; + + 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; + + 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; + } + + case SYS_TIMEOUT: + LOGFQUEUE("voice < SYS_TIMEOUT"); + goto voice_play_clip; + + default: + LOGFQUEUE("voice < default"); + } + } + +voice_play_clip: + + if (voice_remaining == 0 || voicebuf == NULL) + { + 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_queue, Q_VOICE_STOP, 0); + /* Force pcm playback. */ + if (!pcm_is_playing()) + pcmbuf_play_start(); + } + } + + *realsize = MIN(voice_remaining, reqsize); + + if (*realsize == 0) + return NULL; + + return voicebuf; +} /* voice_request_buffer_callback */ + +static void voice_advance_buffer_callback(size_t amount) +{ + amount = MIN(amount, voice_remaining); + voicebuf += amount; + voice_remaining -= amount; +} + +static void voice_advance_buffer_loc_callback(void *ptr) +{ + size_t amount = (size_t)ptr - (size_t)voicebuf; + + voice_advance_buffer_callback(amount); +} + +static off_t voice_mp3_get_filepos_callback(int newtime) +{ + (void)newtime; + + return 0; +} + +static void voice_do_nothing(void) +{ + return; +} + +static bool voice_seek_buffer_callback(size_t newpos) +{ + (void)newpos; + + return false; +} + +static bool voice_request_next_track_callback(void) +{ + ci_voice.new_track = 0; + return true; +} + +static void voice_thread(void) +{ + 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; + + codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice); + + logf("Voice codec finished"); + voice_codec_loaded = false; + mutex_unlock(&mutex_codecthread); + } +} /* voice_thread */ + + +/* --- Codec thread --- */ + static bool codec_pcmbuf_insert_split_callback( const void *ch1, const void *ch2, size_t length) { @@ -452,20 +1106,6 @@ static bool codec_pcmbuf_insert_split_callback( 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); -} - static bool codec_pcmbuf_insert_callback(const char *buf, size_t length) { /* TODO: The audiobuffer API should probably be updated, and be based on @@ -480,20 +1120,14 @@ static bool codec_pcmbuf_insert_callback(const char *buf, size_t length) length); } -static void* get_voice_memory_callback(size_t *size) -{ - *size = 0; - return NULL; -} - -static void* get_codec_memory_callback(size_t *size) +static void* codec_get_memory_callback(size_t *size) { *size = MALLOC_BUFSIZE; return &audiobuf[talk_get_bufsize()]; } -static void pcmbuf_position_callback(size_t size) ICODE_ATTR; -static void pcmbuf_position_callback(size_t size) +static void codec_pcmbuf_position_callback(size_t size) ICODE_ATTR; +static void codec_pcmbuf_position_callback(size_t size) { unsigned int time = size * 1000 / 4 / NATIVE_FREQUENCY + prev_ti->id3.elapsed; @@ -507,11 +1141,6 @@ static void pcmbuf_position_callback(size_t size) prev_ti->id3.elapsed = time; } -static void voice_set_elapsed_callback(unsigned int value) -{ - (void)value; -} - static void codec_set_elapsed_callback(unsigned int value) { unsigned int latency; @@ -532,11 +1161,6 @@ static void codec_set_elapsed_callback(unsigned int value) } } -static void voice_set_offset_callback(size_t value) -{ - (void)value; -} - static void codec_set_offset_callback(size_t value) { unsigned int latency; @@ -551,41 +1175,6 @@ static void codec_set_offset_callback(size_t value) cur_ti->id3.offset = value - latency; } -static bool filebuf_is_lowdata(void) -{ - return filebufused < AUDIO_FILEBUF_CRITICAL; -} - -static bool have_tracks(void) -{ - return track_ridx != track_widx || cur_ti->filesize; -} - -static bool 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 (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) { buf_ridx += amount; @@ -608,14 +1197,6 @@ static void codec_advance_buffer_counters(size_t amount) } } -static size_t voice_filebuf_callback(void *ptr, size_t size) -{ - (void)ptr; - (void)size; - - 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) { @@ -663,118 +1244,6 @@ static size_t codec_filebuf_callback(void *ptr, size_t size) return copy_n; } /* codec_filebuf_callback */ -static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize) -{ - struct event ev; - - if (ci_voice.new_track) - { - *realsize = 0; - return NULL; - } - - 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 - - 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; - - 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; - - 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; - } - - case SYS_TIMEOUT: - LOGFQUEUE("voice < SYS_TIMEOUT"); - goto voice_play_clip; - - default: - LOGFQUEUE("voice < default"); - } - } - -voice_play_clip: - - if (voice_remaining == 0 || voicebuf == NULL) - { - 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(); - } - } - - *realsize = MIN(voice_remaining, reqsize); - - if (*realsize == 0) - return NULL; - - return voicebuf; -} /* voice_request_buffer_callback */ - static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) { size_t short_n, copy_n, buf_rem; @@ -841,8 +1310,561 @@ static int get_codec_base_type(int type) return type; } +static void codec_advance_buffer_callback(size_t amount) +{ + if (amount > cur_ti->available + cur_ti->filerem) + amount = cur_ti->available + cur_ti->filerem; + + while (amount > cur_ti->available && filling) + sleep(1); + + 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 codec_advance_buffer_loc_callback(void *ptr) +{ + size_t amount = (size_t)ptr - (size_t)&filebuf[buf_ridx]; + + codec_advance_buffer_callback(amount); +} + +/* 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) + { + 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; + + /* 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; + + return pos; +} + +static off_t codec_mp3_get_filepos_callback(int newtime) +{ + off_t newpos; + + cur_ti->id3.elapsed = newtime; + newpos = codec_get_file_pos(); + + return newpos; +} + +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); + } + ci.seek_time = 0; +} + +static bool codec_seek_buffer_callback(size_t newpos) +{ + int difference; + + logf("codec_seek_buffer_callback"); + + if (newpos >= cur_ti->filesize) + newpos = cur_ti->filesize - 1; + + difference = newpos - ci.curpos; + if (difference >= 0) + { + /* 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; + + /* 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) + { + case Q_CODEC_REQUEST_COMPLETE: + LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); + 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; + + return true; +} + +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 CODEC_SET_FILEBUF_CHUNKSIZE: + conf_filechunk = (unsigned long)value; + break; + + case CODEC_SET_FILEBUF_PRESEEK: + conf_preseek = (unsigned long)value; + break; + + default: + if (!dsp_configure(setting, value)) { logf("Illegal key:%d", setting); } + } +} + +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); +} + +static void codec_pcmbuf_track_changed_callback(void) +{ + pcmbuf_set_position_callback(NULL); + codec_track_changed(); +} + +static void codec_discard_codec_callback(void) +{ + if (cur_ti->has_codec) + { + cur_ti->has_codec = false; + filebufused -= cur_ti->codecsize; + buf_ridx += cur_ti->codecsize; + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; + } + +#if 0 + /* Check if a buffer desync has happened, log it and stop playback. */ + if (buf_ridx != cur_ti->buf_idx) + { + 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 codec_track_skip_done(bool was_manual) +{ + /* Manual track change (always crossfade or flush audio). */ + if (was_manual) + { + pcmbuf_crossfade_init(true); + LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED"); + queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); + } + /* Automatic track change w/crossfade, if not in "Track Skip Only" mode. */ + else if (pcmbuf_is_crossfade_enabled() && !pcmbuf_is_crossfade_active() + && global_settings.crossfade != 2) + { + pcmbuf_crossfade_init(false); + codec_track_changed(); + } + /* Gapless playback. */ + else + { + pcmbuf_set_position_callback(codec_pcmbuf_position_callback); + pcmbuf_set_event_handler(codec_pcmbuf_track_changed_callback); + } +} + +static bool codec_load_next_track(void) +{ + struct event ev; + + if (ci.seek_time) + codec_seek_complete_callback(); + +#ifdef AB_REPEAT_ENABLE + ab_end_of_track_report(); +#endif + + logf("Request new track"); + + if (ci.new_track == 0) + { + ci.new_track++; + automatic_skip = true; + } + + cpu_boost(true); + LOGFQUEUE("codec > audio Q_AUDIO_CHECK_NEW_TRACK"); + queue_post(&audio_queue, Q_AUDIO_CHECK_NEW_TRACK, 0); + while (1) + { + queue_wait(&codec_callback_queue, &ev); + if (ev.id == Q_CODEC_REQUEST_PENDING) + { + if (!automatic_skip) + pcmbuf_play_stop(); + } + else + break; + } + cpu_boost(false); + switch (ev.id) + { + case Q_CODEC_REQUEST_COMPLETE: + LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); + codec_track_skip_done(!automatic_skip); + return true; + + case Q_CODEC_REQUEST_FAILED: + LOGFQUEUE("codec < Q_CODEC_REQUEST_FAILED"); + ci.new_track = 0; + ci.stop_codec = true; + return false; + + default: + LOGFQUEUE("codec < default"); + ci.stop_codec = true; + return false; + } +} + +static bool codec_request_next_track_callback(void) +{ + int prev_codectype; + + if (ci.stop_codec || !playing) + return false; + + prev_codectype = get_codec_base_type(cur_ti->id3.codectype); + + if (!codec_load_next_track()) + return false; + + /* Check if the next codec is the same file. */ + if (prev_codectype == get_codec_base_type(cur_ti->id3.codectype)) + { + logf("New track loaded"); + codec_discard_codec_callback(); + return true; + } + else + { + logf("New codec:%d/%d", cur_ti->id3.codectype, prev_codectype); + return false; + } +} + +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_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_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: + LOGFQUEUE("codec < Q_ENCODER_LOAD_DISK"); + audio_codec_loaded = false; + if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE) + { + LOGFQUEUE("codec > voice Q_ENCODER_RECORD"); + queue_post(&voice_queue, Q_ENCODER_RECORD, NULL); + } + 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; +#endif + +#ifndef SIMULATOR + case SYS_USB_CONNECTED: + LOGFQUEUE("codec < SYS_USB_CONNECTED"); + queue_clear(&codec_queue); + usb_acknowledge(SYS_USB_CONNECTED_ACK); + if (voice_codec_loaded) + swap_codec(); + usb_wait_for_disconnect(&codec_queue); + break; +#endif + + default: + LOGFQUEUE("codec < default"); + } + + if (audio_codec_loaded) + { + if (ci.stop_codec) + { + status = CODEC_OK; + if (!playing) + pcmbuf_play_stop(); + } + audio_codec_loaded = false; + } + + switch (ev.id) { + case Q_CODEC_LOAD_DISK: + case Q_CODEC_LOAD: + LOGFQUEUE("codec < Q_CODEC_LOAD"); + if (playing) + { + if (ci.new_track || status != CODEC_OK) + { + if (!ci.new_track) + { + logf("Codec failure"); + gui_syncsplash(HZ*2, true, "Codec failure"); + } + + if (!codec_load_next_track()) + { + // queue_post(&codec_queue, Q_AUDIO_STOP, 0); + LOGFQUEUE("codec > audio Q_AUDIO_STOP"); + queue_post(&audio_queue, Q_AUDIO_STOP, 0); + break; + } + } + else + { + logf("Codec finished"); + if (ci.stop_codec) + { + /* Wait for the audio to stop playing before + * triggering the WPS exit */ + while(pcm_is_playing()) + sleep(1); + LOGFQUEUE("codec > audio Q_AUDIO_STOP"); + queue_post(&audio_queue, Q_AUDIO_STOP, 0); + break; + } + } + + if (cur_ti->has_codec) + { + LOGFQUEUE("codec > codec Q_CODEC_LOAD"); + queue_post(&codec_queue, Q_CODEC_LOAD, 0); + } + else + { + const char *codec_fn = get_codec_filename(cur_ti->id3.codectype); + LOGFQUEUE("codec > codec Q_CODEC_LOAD_DISK"); + queue_post(&codec_queue, Q_CODEC_LOAD_DISK, + (void *)codec_fn); + } + } + break; + + default: + LOGFQUEUE("codec < default"); + + } /* 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 buffer_count_tracks(int from_track, int to_track) +static size_t audio_buffer_count_tracks(int from_track, int to_track) { size_t amount = 0; bool need_wrap = to_track < from_track; @@ -863,14 +1885,14 @@ static size_t buffer_count_tracks(int from_track, int to_track) return amount; } -static bool buffer_wind_forward(int new_track_ridx, int old_track_ridx) +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 += buffer_count_tracks(old_track_ridx, new_track_ridx); + amount += audio_buffer_count_tracks(old_track_ridx, new_track_ridx); if (amount > filebufused) return false; @@ -888,7 +1910,7 @@ static bool buffer_wind_forward(int new_track_ridx, int old_track_ridx) return true; } -static bool buffer_wind_backward(int new_track_ridx, int old_track_ridx) +static bool audio_buffer_wind_backward(int new_track_ridx, int old_track_ridx) { /* Available buffer data */ size_t buf_back; @@ -922,7 +1944,7 @@ static bool buffer_wind_backward(int new_track_ridx, int old_track_ridx) } /* Then collect all data from tracks between new and old */ - amount += buffer_count_tracks(new_track_ridx, old_track_ridx); + amount += audio_buffer_count_tracks(new_track_ridx, old_track_ridx); /* Do we have space to make this skip? */ if (amount > buf_back) @@ -953,6 +1975,681 @@ static void audio_update_trackinfo(void) 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 bool audio_read_next_metadata(void) +{ + int fd; + char *trackname; + int next_idx; + int status; + + next_idx = track_widx; + if (tracks[next_idx].taginfo_ready) + { + next_idx++; + next_idx &= MAX_TRACK_MASK; + + if (tracks[next_idx].taginfo_ready) + return true; + } + + 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); + + return status; +} + +/* 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 (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; + } + + 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) + { + audio_read_next_metadata(); + + audio_generate_postbuffer_events(); + filling = false; + +#ifndef SIMULATOR + if (playing) + ata_sleep(); +#endif + } +} + static void audio_rebuffer(void) { logf("Forcing rebuffer"); @@ -1100,7 +2797,7 @@ static void audio_check_new_track(void) /* The track may be in memory, see if it really is */ if (forward) { - if (!buffer_wind_forward(track_ridx, old_track_ridx)) + if (!audio_buffer_wind_forward(track_ridx, old_track_ridx)) audio_rebuffer(); } else @@ -1129,7 +2826,7 @@ static void audio_check_new_track(void) } if (taginfo_ready) { - if (!buffer_wind_backward(track_ridx, old_track_ridx)) + if (!audio_buffer_wind_backward(track_ridx, old_track_ridx)) audio_rebuffer(); } else @@ -1198,208 +2895,6 @@ static void audio_rebuffer_and_seek(size_t newpos) queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); } -static void voice_advance_buffer_callback(size_t amount) -{ - amount = MIN(amount, voice_remaining); - voicebuf += amount; - voice_remaining -= amount; -} - -static void codec_advance_buffer_callback(size_t amount) -{ - if (amount > cur_ti->available + cur_ti->filerem) - amount = cur_ti->available + cur_ti->filerem; - - while (amount > cur_ti->available && filling) - sleep(1); - - 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) -{ - size_t amount = (size_t)ptr - (size_t)voicebuf; - - voice_advance_buffer_callback(amount); -} - -static void codec_advance_buffer_loc_callback(void *ptr) -{ - size_t amount = (size_t)ptr - (size_t)&filebuf[buf_ridx]; - - codec_advance_buffer_callback(amount); -} - -static off_t voice_mp3_get_filepos_callback(int newtime) -{ - (void)newtime; - - return 0; -} - -static off_t codec_mp3_get_filepos_callback(int newtime) -{ - off_t newpos; - - cur_ti->id3.elapsed = newtime; - newpos = mp3_get_file_pos(); - - return newpos; -} - -static void voice_do_nothing(void) -{ - return; -} - -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); - } - ci.seek_time = 0; -} - -static bool voice_seek_buffer_callback(size_t newpos) -{ - (void)newpos; - - return false; -} - -static bool codec_seek_buffer_callback(size_t newpos) -{ - int difference; - - logf("codec_seek_buffer_callback"); - - if (newpos >= cur_ti->filesize) - newpos = cur_ti->filesize - 1; - - difference = newpos - ci.curpos; - if (difference >= 0) - { - /* 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; - - /* 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) - { - case Q_CODEC_REQUEST_COMPLETE: - LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); - 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; - - return true; -} - -static void set_filebuf_watermark(int seconds) -{ - size_t bytes; - - if (current_codec == CODEC_IDX_VOICE) - return; - - if (!filebuf) - return; /* Audio buffers not yet set up */ - - bytes = MAX(cur_ti->id3.bitrate * seconds * (1000/8), conf_watermark); - bytes = MIN(bytes, filebuflen / 2); - conf_watermark = bytes; -} - -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 CODEC_SET_FILEBUF_CHUNKSIZE: - conf_filechunk = (unsigned long)value; - break; - - case CODEC_SET_FILEBUF_PRESEEK: - conf_preseek = (unsigned long)value; - break; - - default: - if (!dsp_configure(setting, value)) { logf("Illegal key:%d", setting); } - } -} - void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3, bool last_track)) { @@ -1417,582 +2912,6 @@ void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3)) track_changed_callback = handler; } -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); -} - -static void pcmbuf_track_changed_callback(void) -{ - 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(); - - if (!queue_empty(&audio_queue)) return true; - - 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; -} - -/* FIXME: This code should be made more generic and move to metadata.c */ -static void 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; - 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 void codec_discard_codec_callback(void) -{ - if (cur_ti->has_codec) - { - cur_ti->has_codec = false; - filebufused -= cur_ti->codecsize; - buf_ridx += cur_ti->codecsize; - if (buf_ridx >= filebuflen) - buf_ridx -= filebuflen; - } - -#if 0 - /* Check if a buffer desync has happened, log it and stop playback. */ - if (buf_ridx != cur_ti->buf_idx) - { - 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 const char * get_codec_filename(int enc_spec) -{ - const char *fname; - int type = enc_spec & CODEC_TYPE_MASK; - int afmt = enc_spec & CODEC_AFMT_MASK; - - if ((unsigned)afmt >= AFMT_NUM_CODECS) - type = AFMT_UNKNOWN | (type & CODEC_TYPE_MASK); - - fname = (type == CODEC_TYPE_DECODER) ? - audio_formats[afmt].codec_fn : audio_formats[afmt].codec_enc_fn; - - logf("%s: %d - %s", - (type == CODEC_TYPE_ENCODER) ? "Encoder" : "Decoder", - afmt, fname ? fname : ""); - - 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 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; -} - -static bool read_next_metadata(void) -{ - int fd; - char *trackname; - int next_idx; - int status; - - next_idx = track_widx; - if (tracks[next_idx].taginfo_ready) - { - next_idx++; - next_idx &= MAX_TRACK_MASK; - - if (tracks[next_idx].taginfo_ready) - return true; - } - - 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); - - return status; -} - -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 (!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; - mp3_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; -} - -/* 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)); - } -} - static void audio_stop_codec_flush(void) { ci.stop_codec = true; @@ -2017,7 +2936,7 @@ static void audio_stop_playback(void) (playlist_end && ci.stop_codec)?NULL:audio_current_track()); } - while (voice_is_playing && !queue_empty(&voice_codec_queue)) + while (voice_is_playing && !queue_empty(&voice_queue)) yield(); filebufused = 0; @@ -2073,230 +2992,11 @@ static void audio_play_start(size_t offset) 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) -{ - /* Manual track change (always crossfade or flush audio). */ - if (was_manual) - { - pcmbuf_crossfade_init(true); - LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED"); - queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); - } - /* Automatic track change w/crossfade, if not in "Track Skip Only" mode. */ - else if (pcmbuf_is_crossfade_enabled() && !pcmbuf_is_crossfade_active() - && global_settings.crossfade != 2) - { - pcmbuf_crossfade_init(false); - codec_track_changed(); - } - /* Gapless playback. */ - else - { - pcmbuf_set_position_callback(pcmbuf_position_callback); - pcmbuf_set_event_handler(pcmbuf_track_changed_callback); - } -} - -static bool codec_load_next_track(void) -{ - struct event ev; - - if (ci.seek_time) - codec_seek_complete_callback(); - -#ifdef AB_REPEAT_ENABLE - ab_end_of_track_report(); -#endif - - logf("Request new track"); - - if (ci.new_track == 0) - { - ci.new_track++; - automatic_skip = true; - } - - cpu_boost(true); - LOGFQUEUE("codec > audio Q_AUDIO_CHECK_NEW_TRACK"); - queue_post(&audio_queue, Q_AUDIO_CHECK_NEW_TRACK, 0); - while (1) - { - queue_wait(&codec_callback_queue, &ev); - if (ev.id == Q_CODEC_REQUEST_PENDING) - { - if (!automatic_skip) - pcmbuf_play_stop(); - } - else - break; - } - cpu_boost(false); - switch (ev.id) - { - case Q_CODEC_REQUEST_COMPLETE: - LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); - codec_track_skip_done(!automatic_skip); - return true; - - case Q_CODEC_REQUEST_FAILED: - LOGFQUEUE("codec < Q_CODEC_REQUEST_FAILED"); - ci.new_track = 0; - ci.stop_codec = true; - return false; - - default: - LOGFQUEUE("codec < default"); - ci.stop_codec = true; - return false; - } -} - -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; - - if (ci.stop_codec || !playing) - return false; - - prev_codectype = get_codec_base_type(cur_ti->id3.codectype); - - if (!codec_load_next_track()) - return false; - - /* Check if the next codec is the same file. */ - if (prev_codectype == get_codec_base_type(cur_ti->id3.codectype)) - { - logf("New track loaded"); - codec_discard_codec_callback(); - return true; - } - else - { - logf("New codec:%d/%d", cur_ti->id3.codectype, prev_codectype); - return false; - } -} /* Invalidates all but currently playing track. */ void audio_invalidate_tracks(void) { - if (have_tracks()) { + if (audio_have_tracks()) { last_peek_offset = 0; playlist_end = false; @@ -2313,7 +3013,7 @@ void audio_invalidate_tracks(void) if (buf_widx >= filebuflen) buf_widx -= filebuflen; - read_next_metadata(); + audio_read_next_metadata(); } } @@ -2321,7 +3021,7 @@ 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()) { + if (audio_have_tracks()) { playlist_end = false; track_widx = track_ridx; audio_clear_track_entries(true, true, true); @@ -2370,12 +3070,148 @@ static void audio_initiate_dir_change(long direction) ci.new_track = direction; } -void audio_thread(void) +static void audio_reset_buffer(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); + + /* 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); + +#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]; + } + 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; + } + + /* Ensure that file buffer is aligned */ + offset = (-(size_t)filebuf) & 3; + filebuf += offset; + filebuflen -= offset; + filebuflen &= ~3; +} + + +#ifdef ROCKBOX_HAS_LOGF +static void audio_test_track_changed_event(struct mp3entry *id3) +{ + (void)id3; + + logf("tce:%s", id3->path); +} +#endif + +static void audio_playback_init(void) +{ + static bool voicetagtrue = true; + struct event ev; + + logf("playback api init"); + pcm_init(); + +#if defined(HAVE_RECORDING) && !defined(SIMULATOR) + /* Set the input multiplexer to Line In */ + pcm_rec_mux(0); +#endif + +#ifdef ROCKBOX_HAS_LOGF + 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 = 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; + ci.request_next_track = codec_request_next_track_callback; + ci.mp3_get_filepos = codec_mp3_get_filepos_callback; + ci.seek_buffer = codec_seek_buffer_callback; + ci.seek_complete = codec_seek_complete_callback; + ci.set_elapsed = codec_set_elapsed_callback; + ci.set_offset = codec_set_offset_callback; + ci.configure = codec_configure_callback; + ci.discard_codec = codec_discard_codec_callback; + + /* Initialize voice codec api. */ + memcpy(&ci_voice, &ci, sizeof(struct codec_api)); + memset(&id3_voice, 0, sizeof(struct mp3entry)); + 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 = 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; + ci_voice.request_next_track = voice_request_next_track_callback; + ci_voice.mp3_get_filepos = voice_mp3_get_filepos_callback; + ci_voice.seek_buffer = voice_seek_buffer_callback; + ci_voice.seek_complete = voice_do_nothing; + ci_voice.set_elapsed = voice_set_elapsed_callback; + ci_voice.set_offset = voice_set_offset_callback; + ci_voice.discard_codec = voice_do_nothing; + ci_voice.taginfo_ready = &voicetagtrue; + ci_voice.id3 = &id3_voice; + id3_voice.frequency = 11200; + id3_voice.length = 1000000L; + + create_thread(codec_thread, codec_stack, sizeof(codec_stack), + codec_thread_name); + + while (1) + { + queue_wait(&audio_queue, &ev); + if (ev.id == Q_AUDIO_POSTINIT) + break ; + +#ifndef SIMULATOR + if (ev.id == SYS_USB_CONNECTED) + { + logf("USB: Audio preinit"); + usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_wait_for_disconnect(&audio_queue); + } +#endif + } + + filebuf = (char *)&audiobuf[MALLOC_BUFSIZE]; /* Will be reset by reset_buffer */ + + audio_set_crossfade(global_settings.crossfade); + + audio_is_initialized = true; + + sound_settings_apply(); +} + +static void audio_thread(void) { struct event ev; /* At first initialize audio system in background. */ - playback_init(); + audio_playback_init(); while (1) { if (filling) @@ -2487,818 +3323,3 @@ void audio_thread(void) } } -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 ; - -#if defined(HAVE_RECORDING) && !defined(SIMULATOR) - case Q_ENCODER_LOAD_DISK: - LOGFQUEUE("codec < Q_ENCODER_LOAD_DISK"); - audio_codec_loaded = false; - if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE) - { - LOGFQUEUE("codec > voice Q_ENCODER_RECORD"); - queue_post(&voice_codec_queue, Q_ENCODER_RECORD, NULL); - } - 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; -#endif - -#ifndef SIMULATOR - case SYS_USB_CONNECTED: - LOGFQUEUE("codec < SYS_USB_CONNECTED"); - queue_clear(&codec_queue); - usb_acknowledge(SYS_USB_CONNECTED_ACK); - if (voice_codec_loaded) - swap_codec(); - usb_wait_for_disconnect(&codec_queue); - break; -#endif - - default: - LOGFQUEUE("codec < default"); - } - - if (audio_codec_loaded) - { - if (ci.stop_codec) - { - status = CODEC_OK; - if (!playing) - pcmbuf_play_stop(); - } - audio_codec_loaded = false; - } - - switch (ev.id) { - case Q_CODEC_LOAD_DISK: - case Q_CODEC_LOAD: - LOGFQUEUE("codec < Q_CODEC_LOAD"); - if (playing) - { - if (ci.new_track || status != CODEC_OK) - { - if (!ci.new_track) - { - logf("Codec failure"); - gui_syncsplash(HZ*2, true, "Codec failure"); - } - - if (!codec_load_next_track()) - { - // queue_post(&codec_queue, Q_AUDIO_STOP, 0); - LOGFQUEUE("codec > audio Q_AUDIO_STOP"); - queue_post(&audio_queue, Q_AUDIO_STOP, 0); - break; - } - } - else - { - logf("Codec finished"); - if (ci.stop_codec) - { - /* Wait for the audio to stop playing before - * triggering the WPS exit */ - while(pcm_is_playing()) - sleep(1); - LOGFQUEUE("codec > audio Q_AUDIO_STOP"); - queue_post(&audio_queue, Q_AUDIO_STOP, 0); - break; - } - } - - if (cur_ti->has_codec) - { - LOGFQUEUE("codec > codec Q_CODEC_LOAD"); - queue_post(&codec_queue, Q_CODEC_LOAD, 0); - } - else - { - const char *codec_fn = get_codec_filename(cur_ti->id3.codectype); - LOGFQUEUE("codec > codec Q_CODEC_LOAD_DISK"); - queue_post(&codec_queue, Q_CODEC_LOAD_DISK, - (void *)codec_fn); - } - } - break; - - default: - LOGFQUEUE("codec < default"); - - } /* end switch */ - } -} - -static void audio_reset_buffer(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); - - /* 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); - -#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]; - } - 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; - } - - /* Ensure that file buffer is aligned */ - offset = (-(size_t)filebuf) & 3; - filebuf += offset; - filebuflen -= offset; - filebuflen &= ~3; -} - -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; - - audio_remove_encoder(); - - LOGFQUEUE("audio > codec Q_ENCODER_LOAD_DISK"); - queue_post(&codec_queue, Q_ENCODER_LOAD_DISK, (void *)enc_fn); - - while (!ci.enc_codec_loaded) - yield(); -#endif - return; - (void)enc_id; -} /* audio_load_encoder */ - -void audio_remove_encoder(void) -{ -#if defined(HAVE_RECORDING) && !defined(SIMULATOR) - /* force encoder codec unload (if previously loaded) */ - if (!ci.enc_codec_loaded) - return; - - ci.stop_codec = true; - while (ci.enc_codec_loaded) - yield(); -#endif -} /* audio_remove_encoder */ - -static void voice_codec_thread(void) -{ - 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; - - codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice); - - logf("Voice codec finished"); - voice_codec_loaded = false; - mutex_unlock(&mutex_codecthread); - } -} /* voice_codec_thread */ - -void voice_init(void) -{ - if (!filebuf) - return; /* Audio buffers not yet set up */ - - if (voice_thread_num >= 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; - } - - 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 */ - -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; - - mp3_play_stop(); - while (voice_is_playing && !queue_empty(&voice_codec_queue)) - yield(); -} /* voice_stop */ - -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; - - if (tracks[cur_idx].taginfo_ready) - return &tracks[cur_idx].id3; - - memset(&temp_id3, 0, sizeof(struct mp3entry)); - - filename = playlist_peek(ci.new_track); - if (!filename) - filename = "No file!"; - -#ifdef HAVE_TC_RAMCACHE - if (tagcache_fill_tags(&temp_id3, filename)) - return &temp_id3; -#endif - - p = strrchr(filename, '/'); - if (!p) - p = filename; - else - p++; - - strncpy(temp_id3.path, p, sizeof(temp_id3.path)-1); - temp_id3.title = &temp_id3.path[0]; - - return &temp_id3; -} - -struct mp3entry* audio_next_track(void) -{ - int next_idx = track_ridx; - - if (!have_tracks()) - return NULL; - - next_idx++; - next_idx &= MAX_TRACK_MASK; - - if (!tracks[next_idx].taginfo_ready) - return NULL; - - return &tracks[next_idx].id3; -} - -bool audio_has_changed_track(void) -{ - if (track_changed) - { - track_changed = false; - return true; - } - - return false; -} - -void audio_play(long offset) -{ - 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(); - - playing = true; - LOGFQUEUE("audio > audio Q_AUDIO_PLAY"); - queue_post(&audio_queue, Q_AUDIO_PLAY, (void *)offset); - } -} - -void audio_stop(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_STOP"); - queue_post(&audio_queue, Q_AUDIO_STOP, 0); - while (playing || audio_codec_loaded) - yield(); -} - -bool mp3_pause_done(void) -{ - return pcm_is_paused(); -} - -void audio_pause(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_PAUSE"); - queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)true); -} - -void audio_resume(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_PAUSE resume"); - queue_post(&audio_queue, Q_AUDIO_PAUSE, (void *)false); -} - -void audio_next(void) -{ - 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); -} - -void audio_prev(void) -{ - if (global_settings.beep) - pcmbuf_beep(5000, 100, 2500*global_settings.beep); - - audio_initiate_track_change(-1); - // queue_post(&audio_queue, Q_AUDIO_SKIP, (void *)-1); -} - -void audio_next_dir(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP 1"); - queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)1); -} - -void audio_prev_dir(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP -1"); - queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, (void *)-1); -} - -void audio_pre_ff_rewind(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_PRE_FF_REWIND"); - queue_post(&audio_queue, Q_AUDIO_PRE_FF_REWIND, 0); -} - -void audio_ff_rewind(long newpos) -{ - LOGFQUEUE("audio > audio Q_AUDIO_FF_REWIND"); - queue_post(&audio_queue, Q_AUDIO_FF_REWIND, (int *)newpos); -} - -void audio_flush_and_reload_tracks(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_FLUSH"); - queue_post(&audio_queue, Q_AUDIO_FLUSH, 0); -} - -void audio_error_clear(void) -{ -} - -int audio_status(void) -{ - int ret = 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; -} - -bool audio_query_poweroff(void) -{ - return !(playing && paused); -} - -int audio_get_file_pos(void) -{ - return 0; -} - - -/* TODO: Copied from mpeg.c. Should be moved somewhere else. */ -static void mp3_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); - } -} - -/* Copied from mpeg.c. Should be moved somewhere else. */ -static int mp3_get_file_pos(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; - - /* 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; - - return pos; -} - -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); -} - -void mp3_play_stop(void) -{ - logf("mp3 > voice Q_VOICE_STOP"); - queue_post(&voice_codec_queue, Q_VOICE_STOP, 0); -} - -void audio_set_buffer_margin(int setting) -{ - 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); -} - -/* 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 - - if (!filebuf) - return; /* Audio buffers not yet set up */ - -#if MEM > 1 - if (enable) - seconds = global_settings.crossfade_fade_out_delay - + global_settings.crossfade_fade_out_duration; - - /* 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 ; - - 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)); - } - - /* 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(); - } -} - -void mpeg_id3_options(bool _v1first) -{ - v1first = _v1first; -} - -#ifdef ROCKBOX_HAS_LOGF -void test_track_changed_event(struct mp3entry *id3) -{ - (void)id3; - - logf("tce:%s", id3->path); -} -#endif - -static void playback_init(void) -{ - static bool voicetagtrue = true; - struct event ev; - - logf("playback api init"); - pcm_init(); - -#if defined(HAVE_RECORDING) && !defined(SIMULATOR) - /* Set the input multiplexer to Line In */ - pcm_rec_mux(0); -#endif - -#ifdef ROCKBOX_HAS_LOGF - audio_set_track_changed_event(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.request_buffer = codec_request_buffer_callback; - ci.advance_buffer = codec_advance_buffer_callback; - ci.advance_buffer_loc = codec_advance_buffer_loc_callback; - ci.request_next_track = codec_request_next_track_callback; - ci.mp3_get_filepos = codec_mp3_get_filepos_callback; - ci.seek_buffer = codec_seek_buffer_callback; - ci.seek_complete = codec_seek_complete_callback; - ci.set_elapsed = codec_set_elapsed_callback; - ci.set_offset = codec_set_offset_callback; - ci.configure = codec_configure_callback; - ci.discard_codec = codec_discard_codec_callback; - - /* Initialize voice codec api. */ - memcpy(&ci_voice, &ci, sizeof(struct codec_api)); - memset(&id3_voice, 0, sizeof(struct mp3entry)); - 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.request_buffer = voice_request_buffer_callback; - ci_voice.advance_buffer = voice_advance_buffer_callback; - ci_voice.advance_buffer_loc = voice_advance_buffer_loc_callback; - ci_voice.request_next_track = voice_request_next_track_callback; - ci_voice.mp3_get_filepos = voice_mp3_get_filepos_callback; - ci_voice.seek_buffer = voice_seek_buffer_callback; - ci_voice.seek_complete = voice_do_nothing; - ci_voice.set_elapsed = voice_set_elapsed_callback; - ci_voice.set_offset = voice_set_offset_callback; - ci_voice.discard_codec = voice_do_nothing; - ci_voice.taginfo_ready = &voicetagtrue; - ci_voice.id3 = &id3_voice; - id3_voice.frequency = 11200; - id3_voice.length = 1000000L; - - create_thread(codec_thread, codec_stack, sizeof(codec_stack), - codec_thread_name); - - while (1) - { - queue_wait(&audio_queue, &ev); - if (ev.id == Q_AUDIO_POSTINIT) - break ; - -#ifndef SIMULATOR - if (ev.id == SYS_USB_CONNECTED) - { - logf("USB: Audio preinit"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); - usb_wait_for_disconnect(&audio_queue); - } -#endif - } - - filebuf = (char *)&audiobuf[MALLOC_BUFSIZE]; /* Will be reset by reset_buffer */ - - audio_set_crossfade(global_settings.crossfade); - - audio_is_initialized = true; - - sound_settings_apply(); -} - -void audio_preinit(void) -{ - logf("playback system pre-init"); - - 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]; - - mutex_init(&mutex_codecthread); - - queue_init(&audio_queue); - queue_init(&codec_queue); - /* clear, not init to create a private queue */ - queue_clear(&codec_callback_queue); - - create_thread(audio_thread, audio_stack, sizeof(audio_stack), - audio_thread_name); -} - -void audio_init(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_POSTINIT"); - queue_post(&audio_queue, Q_AUDIO_POSTINIT, 0); -} -