diff options
| author | Steve Bavin <pondlife@pondlife.me> | 2006-09-01 07:59:31 +0000 |
|---|---|---|
| committer | Steve Bavin <pondlife@pondlife.me> | 2006-09-01 07:59:31 +0000 |
| commit | 3cc46e762065c1d7f6bceebaa39572e30ca78b2b (patch) | |
| tree | 80fd48076d09c5c3f61eb59f2afe8e692a786acf | |
| parent | 1bb8657a8f1a3145ae0d66068f142008012da1cb (diff) | |
| download | rockbox-3cc46e762065c1d7f6bceebaa39572e30ca78b2b.zip rockbox-3cc46e762065c1d7f6bceebaa39572e30ca78b2b.tar.gz rockbox-3cc46e762065c1d7f6bceebaa39572e30ca78b2b.tar.bz2 rockbox-3cc46e762065c1d7f6bceebaa39572e30ca78b2b.tar.xz | |
Rearrangement of playback.c to group routines by thread
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10838 a1c6a512-1295-4272-9138-f99709370657
| -rw-r--r-- | apps/playback.c | 3453 |
1 files changed, 1737 insertions, 1716 deletions
diff --git a/apps/playback.c b/apps/playback.c index ed8bb10..352c99b 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 : "<unknown>"); + + 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,370 +1310,6 @@ static int get_codec_base_type(int type) return type; } -/* Count the data BETWEEN the selected tracks */ -static size_t buffer_count_tracks(int from_track, int to_track) -{ - size_t amount = 0; - bool need_wrap = to_track < from_track; - - while (1) - { - if (++from_track >= MAX_TRACK) - { - from_track -= MAX_TRACK; - need_wrap = false; - } - - if (from_track >= to_track && !need_wrap) - break; - - amount += tracks[from_track].codecsize + tracks[from_track].filesize; - } - return amount; -} - -static bool 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); - - if (amount > filebufused) - return false; - - logf("bwf:%ldB",amount); - - /* Wind the buffer to the beginning of the target track or its codec */ - buf_ridx += amount; - filebufused -= amount; - - /* Check and handle buffer wrapping */ - if (buf_ridx >= filebuflen) - buf_ridx -= filebuflen; - - return true; -} - -static bool buffer_wind_backward(int new_track_ridx, int old_track_ridx) -{ - /* Available buffer data */ - size_t buf_back; - /* Start with the previously playing track's data and our data */ - size_t amount; - - buf_back = buf_ridx; - amount = ci.curpos; - if (buf_ridx < buf_widx) - buf_back += filebuflen; - buf_back -= buf_widx; - - /* If we're not just resetting the current track */ - if (new_track_ridx != old_track_ridx) - { - /* Need to wind to before the old track's codec and our filesize */ - amount += tracks[old_track_ridx].codecsize; - amount += tracks[new_track_ridx].filesize; - - /* Rewind the old track to its beginning */ - tracks[old_track_ridx].available = - tracks[old_track_ridx].filesize - tracks[old_track_ridx].filerem; - } - - /* If the codec was ever buffered */ - if (tracks[new_track_ridx].codecsize) - { - /* Add the codec to the needed size */ - amount += tracks[new_track_ridx].codecsize; - tracks[new_track_ridx].has_codec = true; - } - - /* Then collect all data from tracks between new and old */ - amount += buffer_count_tracks(new_track_ridx, old_track_ridx); - - /* Do we have space to make this skip? */ - if (amount > buf_back) - return false; - - logf("bwb:%ldB",amount); - - /* Check and handle buffer wrapping */ - if (amount > buf_ridx) - buf_ridx += filebuflen; - /* Rewind the buffer to the beginning of the target track or its codec */ - buf_ridx -= amount; - filebufused += amount; - - /* Reset to the beginning of the new track */ - tracks[new_track_ridx].available = tracks[new_track_ridx].filesize; - - return true; -} - -static void audio_update_trackinfo(void) -{ - ci.filesize = cur_ti->filesize; - cur_ti->id3.elapsed = 0; - cur_ti->id3.offset = 0; - ci.id3 = &cur_ti->id3; - ci.curpos = 0; - ci.taginfo_ready = &cur_ti->taginfo_ready; -} - -static void audio_rebuffer(void) -{ - logf("Forcing rebuffer"); - - /* Notify the codec that this will take a while */ - /* Currently this can cause some problems (logf in reverse order): - * Codec load error:-1 - * Codec load disk - * Codec: Unsupported - * Codec finished - * New codec:0/3 - * Clearing tracks:7/7, 1 - * Forcing rebuffer - * Check new track buffer - * Request new track - * Clearing tracks:5/5, 0 - * Starting buffer fill - * Clearing tracks:5/5, 1 - * Re-buffering song w/seek - */ - //if (!filling) - // queue_post(&codec_callback_queue, Q_CODEC_REQUEST_PENDING, 0); - - /* Stop in progress fill, and clear open file descriptor */ - if (current_fd >= 0) - { - close(current_fd); - current_fd = -1; - } - filling = false; - - /* Reset buffer and track pointers */ - tracks[track_ridx].buf_idx = buf_ridx = buf_widx = 0; - track_widx = track_ridx; - cur_ti = &tracks[track_ridx]; - audio_clear_track_entries(true, true, false); - filebufused = 0; - cur_ti->available = 0; - - /* Fill the buffer */ - last_peek_offset = -1; - cur_ti->filesize = 0; - cur_ti->start_pos = 0; - ci.curpos = 0; - - if (!cur_ti->taginfo_ready) - memset(&cur_ti->id3, 0, sizeof(struct mp3entry)); - - audio_fill_file_buffer(false, true, 0); -} - -static void audio_check_new_track(void) -{ - int track_count = audio_track_count(); - int old_track_ridx = track_ridx; - bool forward; - - if (dir_skip) - { - dir_skip = false; - if (playlist_next_dir(ci.new_track)) - { - ci.new_track = 0; - cur_ti->taginfo_ready = false; - audio_rebuffer(); - goto skip_done; - } - else - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } - } - - if (new_playlist) - ci.new_track = 0; - - /* If the playlist isn't that big */ - if (!playlist_check(ci.new_track)) - { - if (ci.new_track >= 0) - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } - /* Find the beginning backward if the user over-skips it */ - while (!playlist_check(++ci.new_track)) - if (ci.new_track >= 0) - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } - } - /* Update the playlist */ - last_peek_offset -= ci.new_track; - - if (playlist_next(ci.new_track) < 0) - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } - - if (new_playlist) - { - ci.new_track = 1; - new_playlist = false; - } - - track_ridx += ci.new_track; - track_ridx &= MAX_TRACK_MASK; - - /* Save the old track */ - prev_ti = cur_ti; - /* Move to the new track */ - cur_ti = &tracks[track_ridx]; - - if (automatic_skip) - playlist_end = false; - - track_changed = !automatic_skip; - - /* If it is not safe to even skip this many track entries */ - if (ci.new_track >= track_count || ci.new_track <= track_count - MAX_TRACK) - { - ci.new_track = 0; - cur_ti->taginfo_ready = false; - audio_rebuffer(); - goto skip_done; - } - - forward = ci.new_track > 0; - ci.new_track = 0; - - /* If the target track is clearly not in memory */ - if (cur_ti->filesize == 0 || !cur_ti->taginfo_ready) - { - audio_rebuffer(); - goto skip_done; - } - - /* The track may be in memory, see if it really is */ - if (forward) - { - if (!buffer_wind_forward(track_ridx, old_track_ridx)) - audio_rebuffer(); - } - else - { - int cur_idx = track_ridx; - bool taginfo_ready = true; - bool wrap = track_ridx > old_track_ridx; - - while (1) - { - cur_idx++; - cur_idx &= MAX_TRACK_MASK; - if (!(wrap || cur_idx < old_track_ridx)) - break; - - /* If we hit a track in between without valid tag info, bail */ - if (!tracks[cur_idx].taginfo_ready) - { - taginfo_ready = false; - break; - } - - tracks[cur_idx].available = tracks[cur_idx].filesize; - if (tracks[cur_idx].codecsize) - tracks[cur_idx].has_codec = true; - } - if (taginfo_ready) - { - if (!buffer_wind_backward(track_ridx, old_track_ridx)) - audio_rebuffer(); - } - else - { - cur_ti->taginfo_ready = false; - audio_rebuffer(); - } - } - -skip_done: - audio_update_trackinfo(); - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); -} - -static void audio_rebuffer_and_seek(size_t newpos) -{ - int fd; - char *trackname; - - trackname = playlist_peek(0); - /* (Re-)open current track's file handle. */ - - fd = open(trackname, O_RDONLY); - if (fd < 0) - { - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); - queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); - return; - } - - if (current_fd >= 0) - close(current_fd); - current_fd = fd; - - playlist_end = false; - - ci.curpos = newpos; - - /* Clear codec buffer. */ - track_widx = track_ridx; - filebufused = 0; - tracks[track_widx].buf_idx = buf_widx = buf_ridx = 0; - - last_peek_offset = 0; - filling = false; - audio_initialize_buffer_fill(true); - filling = true; - - if (newpos > conf_preseek) { - buf_ridx += conf_preseek; - cur_ti->start_pos = newpos - conf_preseek; - } - else - { - buf_ridx += newpos; - cur_ti->start_pos = 0; - } - - cur_ti->filerem = cur_ti->filesize - cur_ti->start_pos; - cur_ti->available = 0; - - lseek(current_fd, cur_ti->start_pos, SEEK_SET); - - LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); - 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) @@ -1245,13 +1350,6 @@ static void codec_advance_buffer_callback(size_t 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]; @@ -1259,11 +1357,60 @@ static void codec_advance_buffer_loc_callback(void *ptr) codec_advance_buffer_callback(amount); } -static off_t voice_mp3_get_filepos_callback(int newtime) +/* Copied from mpeg.c. Should be moved somewhere else. */ +static int codec_get_file_pos(void) { - (void)newtime; - - return 0; + 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) @@ -1271,16 +1418,11 @@ static off_t codec_mp3_get_filepos_callback(int newtime) off_t newpos; cur_ti->id3.elapsed = newtime; - newpos = mp3_get_file_pos(); + newpos = codec_get_file_pos(); return newpos; } -static void voice_do_nothing(void) -{ - return; -} - static void codec_seek_complete_callback(void) { logf("seek_complete"); @@ -1296,13 +1438,6 @@ static void codec_seek_complete_callback(void) 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; @@ -1364,21 +1499,6 @@ static bool codec_seek_buffer_callback(size_t newpos) 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) { @@ -1400,35 +1520,459 @@ static void codec_configure_callback(int setting, void *value) } } -void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3, - bool last_track)) +static void codec_track_changed(void) { - track_buffer_callback = handler; + automatic_skip = false; + track_changed = true; + LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED"); + queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); } -void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3, - bool last_track)) +static void codec_pcmbuf_track_changed_callback(void) { - track_unbuffer_callback = handler; + pcmbuf_set_position_callback(NULL); + codec_track_changed(); } -void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3)) +static void codec_discard_codec_callback(void) { - track_changed_callback = handler; + 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_changed(void) +static void codec_track_skip_done(bool was_manual) { - automatic_skip = false; - track_changed = true; - LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED"); - queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); + /* 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 void pcmbuf_track_changed_callback(void) +static bool codec_load_next_track(void) { - pcmbuf_set_position_callback(NULL); - codec_track_changed(); + 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 audio_buffer_count_tracks(int from_track, int to_track) +{ + size_t amount = 0; + bool need_wrap = to_track < from_track; + + while (1) + { + if (++from_track >= MAX_TRACK) + { + from_track -= MAX_TRACK; + need_wrap = false; + } + + if (from_track >= to_track && !need_wrap) + break; + + amount += tracks[from_track].codecsize + tracks[from_track].filesize; + } + return amount; +} + +static bool audio_buffer_wind_forward(int new_track_ridx, int old_track_ridx) +{ + size_t amount; + + /* Start with the remainder of the previously playing track */ + amount = tracks[old_track_ridx].filesize - ci.curpos; + /* Then collect all data from tracks in between them */ + amount += audio_buffer_count_tracks(old_track_ridx, new_track_ridx); + + if (amount > filebufused) + return false; + + logf("bwf:%ldB",amount); + + /* Wind the buffer to the beginning of the target track or its codec */ + buf_ridx += amount; + filebufused -= amount; + + /* Check and handle buffer wrapping */ + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; + + return true; +} + +static bool audio_buffer_wind_backward(int new_track_ridx, int old_track_ridx) +{ + /* Available buffer data */ + size_t buf_back; + /* Start with the previously playing track's data and our data */ + size_t amount; + + buf_back = buf_ridx; + amount = ci.curpos; + if (buf_ridx < buf_widx) + buf_back += filebuflen; + buf_back -= buf_widx; + + /* If we're not just resetting the current track */ + if (new_track_ridx != old_track_ridx) + { + /* Need to wind to before the old track's codec and our filesize */ + amount += tracks[old_track_ridx].codecsize; + amount += tracks[new_track_ridx].filesize; + + /* Rewind the old track to its beginning */ + tracks[old_track_ridx].available = + tracks[old_track_ridx].filesize - tracks[old_track_ridx].filerem; + } + + /* If the codec was ever buffered */ + if (tracks[new_track_ridx].codecsize) + { + /* Add the codec to the needed size */ + amount += tracks[new_track_ridx].codecsize; + tracks[new_track_ridx].has_codec = true; + } + + /* Then collect all data from tracks between new and old */ + amount += audio_buffer_count_tracks(new_track_ridx, old_track_ridx); + + /* Do we have space to make this skip? */ + if (amount > buf_back) + return false; + + logf("bwb:%ldB",amount); + + /* Check and handle buffer wrapping */ + if (amount > buf_ridx) + buf_ridx += filebuflen; + /* Rewind the buffer to the beginning of the target track or its codec */ + buf_ridx -= amount; + filebufused += amount; + + /* Reset to the beginning of the new track */ + tracks[new_track_ridx].available = tracks[new_track_ridx].filesize; + + return true; +} + +static void audio_update_trackinfo(void) +{ + ci.filesize = cur_ti->filesize; + cur_ti->id3.elapsed = 0; + cur_ti->id3.offset = 0; + ci.id3 = &cur_ti->id3; + ci.curpos = 0; + ci.taginfo_ready = &cur_ti->taginfo_ready; } /* Yield to codecs for as long as possible if they are in need of data @@ -1441,7 +1985,7 @@ static bool audio_yield_codecs(void) if (!queue_empty(&audio_queue)) return true; while ((pcmbuf_is_crossfade_active() || pcmbuf_is_lowdata()) - && !ci.stop_codec && playing && !filebuf_is_lowdata()) + && !ci.stop_codec && playing && !audio_filebuf_is_lowdata()) { sleep(1); if (!queue_empty(&audio_queue)) return true; @@ -1450,8 +1994,64 @@ static bool audio_yield_codecs(void) 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 strip_id3v1_tag(void) +static void audio_strip_id3v1_tag(void) { int i; static const unsigned char tag[] = "TAG"; @@ -1555,7 +2155,7 @@ static void audio_read_file(bool quick) logf("Finished buf:%dB", tracks[track_widx].filesize); close(current_fd); current_fd = -1; - strip_id3v1_tag(); + audio_strip_id3v1_tag(); track_widx++; track_widx &= MAX_TRACK_MASK; @@ -1569,54 +2169,6 @@ static void audio_read_file(bool quick) } } -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 : "<unknown>"); - - return fname; -} /* get_codec_filename */ - static bool audio_loadcodec(bool start_play) { size_t size; @@ -1716,45 +2268,55 @@ static bool audio_loadcodec(bool start_play) return true; } -static bool read_next_metadata(void) +/* TODO: Copied from mpeg.c. Should be moved somewhere else. */ +static void audio_set_elapsed(struct mp3entry* id3) { - int fd; - char *trackname; - int next_idx; - int status; + if ( id3->vbr ) { + if ( id3->has_toc ) { + /* calculate elapsed time using TOC */ + int i; + unsigned int remainder, plen, relpos, nextpos; - next_idx = track_widx; - if (tracks[next_idx].taginfo_ready) - { - next_idx++; - next_idx &= MAX_TRACK_MASK; + /* find wich percent we're at */ + for (i=0; i<100; i++ ) + if ( id3->offset < id3->toc[i] * (id3->filesize / 256) ) + break; - if (tracks[next_idx].taginfo_ready) - return true; - } + i--; + if (i < 0) + i = 0; - trackname = playlist_peek(last_peek_offset + 1); - if (!trackname) - return false; + relpos = id3->toc[i]; - fd = open(trackname, O_RDONLY); - if (fd < 0) - return false; + if (i < 99) + nextpos = id3->toc[i+1]; + else + nextpos = 256; - status = get_metadata(&tracks[next_idx],fd,trackname,v1first); - /* Preload the glyphs in the tags */ - if (status) + 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 { - 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); + /* constant bitrate, use exact calculation */ + if (id3->bitrate != 0) + id3->elapsed = id3->offset / (id3->bitrate / 8); } - close(fd); - - return status; } static bool audio_load_track(int offset, bool start_play, bool rebuffer) @@ -1766,7 +2328,7 @@ static bool audio_load_track(int offset, bool start_play, bool rebuffer) /* 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()) + if (!audio_have_free_tracks()) { logf("No free tracks"); return false; @@ -1902,7 +2464,7 @@ static bool audio_load_track(int offset, bool start_play, bool rebuffer) case AFMT_MPA_L3: lseek(current_fd, offset, SEEK_SET); tracks[track_widx].id3.offset = offset; - mp3_set_elapsed(&tracks[track_widx].id3); + audio_set_elapsed(&tracks[track_widx].id3); tracks[track_widx].filerem = size - offset; ci.curpos = offset; tracks[track_widx].start_pos = offset; @@ -1937,140 +2499,45 @@ static bool audio_load_track(int offset, bool start_play, bool 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) +static bool audio_read_next_metadata(void) { - ci.stop_codec = true; - pcmbuf_pause(true); - while (audio_codec_loaded) - yield(); - /* If the audio codec is not loaded any more, and the audio is still - * playing, it is now and _only_ now safe to call this function from the - * audio thread */ - if (pcm_is_playing()) - pcmbuf_play_stop(); - pcmbuf_pause(paused); -} + int fd; + char *trackname; + int next_idx; + int status; -static void audio_stop_playback(void) -{ - /* If we were playing, save resume information */ - if (playing) + next_idx = track_widx; + if (tracks[next_idx].taginfo_ready) { - /* Save the current playing spot, or NULL if the playlist has ended */ - playlist_update_resume_info( - (playlist_end && ci.stop_codec)?NULL:audio_current_track()); - } + next_idx++; + next_idx &= MAX_TRACK_MASK; - while (voice_is_playing && !queue_empty(&voice_codec_queue)) - yield(); - - filebufused = 0; - playing = false; - filling = false; - paused = false; - audio_stop_codec_flush(); - - if (current_fd >= 0) - { - close(current_fd); - current_fd = -1; + if (tracks[next_idx].taginfo_ready) + return true; } - /* Mark all entries null. */ - audio_clear_track_entries(true, false, false); - memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); -} - -static void audio_play_start(size_t offset) -{ -#if defined(HAVE_RECORDING) || defined(CONFIG_TUNER) - rec_set_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); -#endif - - /* Wait for any previously playing audio to flush - TODO: Not necessary? */ - while (audio_codec_loaded) - audio_stop_codec_flush(); - - track_changed = true; - playlist_end = false; + trackname = playlist_peek(last_peek_offset + 1); + if (!trackname) + return false; - playing = true; - ci.new_track = 0; - ci.seek_time = 0; + fd = open(trackname, O_RDONLY); + if (fd < 0) + return false; - if (current_fd >= 0) + status = get_metadata(&tracks[next_idx],fd,trackname,v1first); + /* Preload the glyphs in the tags */ + if (status) { - close(current_fd); - current_fd = -1; + 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); - sound_set_volume(global_settings.volume); - track_widx = track_ridx = 0; - buf_ridx = buf_widx = 0; - filebufused = 0; - - /* Mark all entries null. */ - memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); - - last_peek_offset = -1; - - audio_fill_file_buffer(true, false, offset); + return status; } /* Send callback events to notify about new tracks. */ @@ -2081,7 +2548,7 @@ static void audio_generate_postbuffer_events(void) logf("Postbuffer:%d/%d",track_ridx,track_widx); - if (have_tracks()) + if (audio_have_tracks()) { cur_idx = track_ridx; @@ -2171,7 +2638,7 @@ static void audio_fill_file_buffer( /* If we're done buffering */ if (fill_bytesleft == 0) { - read_next_metadata(); + audio_read_next_metadata(); audio_generate_postbuffer_events(); filling = false; @@ -2183,120 +2650,353 @@ static void audio_fill_file_buffer( } } -static void codec_track_skip_done(bool was_manual) +static void audio_rebuffer(void) { - /* Manual track change (always crossfade or flush audio). */ - if (was_manual) + logf("Forcing rebuffer"); + + /* Notify the codec that this will take a while */ + /* Currently this can cause some problems (logf in reverse order): + * Codec load error:-1 + * Codec load disk + * Codec: Unsupported + * Codec finished + * New codec:0/3 + * Clearing tracks:7/7, 1 + * Forcing rebuffer + * Check new track buffer + * Request new track + * Clearing tracks:5/5, 0 + * Starting buffer fill + * Clearing tracks:5/5, 1 + * Re-buffering song w/seek + */ + //if (!filling) + // queue_post(&codec_callback_queue, Q_CODEC_REQUEST_PENDING, 0); + + /* Stop in progress fill, and clear open file descriptor */ + if (current_fd >= 0) { - pcmbuf_crossfade_init(true); - LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED"); - queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); + close(current_fd); + current_fd = -1; } - /* 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) + filling = false; + + /* Reset buffer and track pointers */ + tracks[track_ridx].buf_idx = buf_ridx = buf_widx = 0; + track_widx = track_ridx; + cur_ti = &tracks[track_ridx]; + audio_clear_track_entries(true, true, false); + filebufused = 0; + cur_ti->available = 0; + + /* Fill the buffer */ + last_peek_offset = -1; + cur_ti->filesize = 0; + cur_ti->start_pos = 0; + ci.curpos = 0; + + if (!cur_ti->taginfo_ready) + memset(&cur_ti->id3, 0, sizeof(struct mp3entry)); + + audio_fill_file_buffer(false, true, 0); +} + +static void audio_check_new_track(void) +{ + int track_count = audio_track_count(); + int old_track_ridx = track_ridx; + bool forward; + + if (dir_skip) { - pcmbuf_crossfade_init(false); - codec_track_changed(); + dir_skip = false; + if (playlist_next_dir(ci.new_track)) + { + ci.new_track = 0; + cur_ti->taginfo_ready = false; + audio_rebuffer(); + goto skip_done; + } + else + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } } - /* Gapless playback. */ - else + + if (new_playlist) + ci.new_track = 0; + + /* If the playlist isn't that big */ + if (!playlist_check(ci.new_track)) { - pcmbuf_set_position_callback(pcmbuf_position_callback); - pcmbuf_set_event_handler(pcmbuf_track_changed_callback); + if (ci.new_track >= 0) + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } + /* Find the beginning backward if the user over-skips it */ + while (!playlist_check(++ci.new_track)) + if (ci.new_track >= 0) + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } } -} + /* Update the playlist */ + last_peek_offset -= ci.new_track; -static bool codec_load_next_track(void) -{ - struct event ev; + if (playlist_next(ci.new_track) < 0) + { + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } - if (ci.seek_time) - codec_seek_complete_callback(); + if (new_playlist) + { + ci.new_track = 1; + new_playlist = false; + } -#ifdef AB_REPEAT_ENABLE - ab_end_of_track_report(); -#endif + track_ridx += ci.new_track; + track_ridx &= MAX_TRACK_MASK; - logf("Request new track"); + /* Save the old track */ + prev_ti = cur_ti; + /* Move to the new track */ + cur_ti = &tracks[track_ridx]; - if (ci.new_track == 0) + if (automatic_skip) + playlist_end = false; + + track_changed = !automatic_skip; + + /* If it is not safe to even skip this many track entries */ + if (ci.new_track >= track_count || ci.new_track <= track_count - MAX_TRACK) { - ci.new_track++; - automatic_skip = true; + ci.new_track = 0; + cur_ti->taginfo_ready = false; + audio_rebuffer(); + goto skip_done; } - - cpu_boost(true); - LOGFQUEUE("codec > audio Q_AUDIO_CHECK_NEW_TRACK"); - queue_post(&audio_queue, Q_AUDIO_CHECK_NEW_TRACK, 0); - while (1) + + forward = ci.new_track > 0; + ci.new_track = 0; + + /* If the target track is clearly not in memory */ + if (cur_ti->filesize == 0 || !cur_ti->taginfo_ready) { - queue_wait(&codec_callback_queue, &ev); - if (ev.id == Q_CODEC_REQUEST_PENDING) + audio_rebuffer(); + goto skip_done; + } + + /* The track may be in memory, see if it really is */ + if (forward) + { + if (!audio_buffer_wind_forward(track_ridx, old_track_ridx)) + audio_rebuffer(); + } + else + { + int cur_idx = track_ridx; + bool taginfo_ready = true; + bool wrap = track_ridx > old_track_ridx; + + while (1) { - if (!automatic_skip) - pcmbuf_play_stop(); + cur_idx++; + cur_idx &= MAX_TRACK_MASK; + if (!(wrap || cur_idx < old_track_ridx)) + break; + + /* If we hit a track in between without valid tag info, bail */ + if (!tracks[cur_idx].taginfo_ready) + { + taginfo_ready = false; + break; + } + + tracks[cur_idx].available = tracks[cur_idx].filesize; + if (tracks[cur_idx].codecsize) + tracks[cur_idx].has_codec = true; + } + if (taginfo_ready) + { + if (!audio_buffer_wind_backward(track_ridx, old_track_ridx)) + audio_rebuffer(); } else - break; + { + cur_ti->taginfo_ready = false; + audio_rebuffer(); + } } - cpu_boost(false); - switch (ev.id) + +skip_done: + audio_update_trackinfo(); + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); +} + +static void audio_rebuffer_and_seek(size_t newpos) +{ + int fd; + char *trackname; + + trackname = playlist_peek(0); + /* (Re-)open current track's file handle. */ + + fd = open(trackname, O_RDONLY); + if (fd < 0) { - case Q_CODEC_REQUEST_COMPLETE: - LOGFQUEUE("codec < Q_CODEC_REQUEST_COMPLETE"); - codec_track_skip_done(!automatic_skip); - return true; + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_FAILED"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_FAILED, 0); + return; + } + + if (current_fd >= 0) + close(current_fd); + current_fd = fd; - case Q_CODEC_REQUEST_FAILED: - LOGFQUEUE("codec < Q_CODEC_REQUEST_FAILED"); - ci.new_track = 0; - ci.stop_codec = true; - return false; + playlist_end = false; - default: - LOGFQUEUE("codec < default"); - ci.stop_codec = true; - return false; + ci.curpos = newpos; + + /* Clear codec buffer. */ + track_widx = track_ridx; + filebufused = 0; + tracks[track_widx].buf_idx = buf_widx = buf_ridx = 0; + + last_peek_offset = 0; + filling = false; + audio_initialize_buffer_fill(true); + filling = true; + + if (newpos > conf_preseek) { + buf_ridx += conf_preseek; + cur_ti->start_pos = newpos - conf_preseek; + } + else + { + buf_ridx += newpos; + cur_ti->start_pos = 0; } + + cur_ti->filerem = cur_ti->filesize - cur_ti->start_pos; + cur_ti->available = 0; + + lseek(current_fd, cur_ti->start_pos, SEEK_SET); + + LOGFQUEUE("audio > codec Q_CODEC_REQUEST_COMPLETE"); + queue_post(&codec_callback_queue, Q_CODEC_REQUEST_COMPLETE, 0); } -static bool voice_request_next_track_callback(void) +void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3, + bool last_track)) { - ci_voice.new_track = 0; - return true; + track_buffer_callback = handler; } -static bool codec_request_next_track_callback(void) +void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3, + bool last_track)) { - int prev_codectype; + track_unbuffer_callback = handler; +} - if (ci.stop_codec || !playing) - return false; +void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3)) +{ + track_changed_callback = handler; +} - prev_codectype = get_codec_base_type(cur_ti->id3.codectype); +static void audio_stop_codec_flush(void) +{ + ci.stop_codec = true; + pcmbuf_pause(true); + while (audio_codec_loaded) + yield(); + /* If the audio codec is not loaded any more, and the audio is still + * playing, it is now and _only_ now safe to call this function from the + * audio thread */ + if (pcm_is_playing()) + pcmbuf_play_stop(); + pcmbuf_pause(paused); +} - if (!codec_load_next_track()) - return false; +static void audio_stop_playback(void) +{ + /* If we were playing, save resume information */ + if (playing) + { + /* Save the current playing spot, or NULL if the playlist has ended */ + playlist_update_resume_info( + (playlist_end && ci.stop_codec)?NULL:audio_current_track()); + } - /* Check if the next codec is the same file. */ - if (prev_codectype == get_codec_base_type(cur_ti->id3.codectype)) + while (voice_is_playing && !queue_empty(&voice_queue)) + yield(); + + filebufused = 0; + playing = false; + filling = false; + paused = false; + audio_stop_codec_flush(); + + if (current_fd >= 0) { - logf("New track loaded"); - codec_discard_codec_callback(); - return true; + close(current_fd); + current_fd = -1; } - else + + /* Mark all entries null. */ + audio_clear_track_entries(true, false, false); + memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); +} + +static void audio_play_start(size_t offset) +{ +#if defined(HAVE_RECORDING) || defined(CONFIG_TUNER) + rec_set_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); +#endif + + /* Wait for any previously playing audio to flush - TODO: Not necessary? */ + while (audio_codec_loaded) + audio_stop_codec_flush(); + + track_changed = true; + playlist_end = false; + + playing = true; + ci.new_track = 0; + ci.seek_time = 0; + + if (current_fd >= 0) { - logf("New codec:%d/%d", cur_ti->id3.codectype, prev_codectype); - return false; + close(current_fd); + current_fd = -1; } + + sound_set_volume(global_settings.volume); + track_widx = track_ridx = 0; + buf_ridx = buf_widx = 0; + filebufused = 0; + + /* Mark all entries null. */ + memset(tracks, 0, sizeof(struct track_info) * MAX_TRACK); + + last_peek_offset = -1; + + audio_fill_file_buffer(true, false, offset); } + /* 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,280 +3070,6 @@ static void audio_initiate_dir_change(long direction) ci.new_track = direction; } -void audio_thread(void) -{ - struct event ev; - - /* At first initialize audio system in background. */ - playback_init(); - - while (1) { - if (filling) - { - queue_wait_w_tmo(&audio_queue, &ev, 0); - if (ev.id == SYS_TIMEOUT) - ev.id = Q_AUDIO_FILL_BUFFER; - } - else - queue_wait_w_tmo(&audio_queue, &ev, HZ); - - switch (ev.id) { - case Q_AUDIO_FILL_BUFFER: - LOGFQUEUE("audio < Q_AUDIO_FILL_BUFFER"); - if (!filling) - if (!playing || playlist_end || ci.stop_codec) - break; - audio_fill_file_buffer(false, false, 0); - break; - - case Q_AUDIO_PLAY: - LOGFQUEUE("audio < Q_AUDIO_PLAY"); - audio_clear_track_entries(true, false, true); - audio_play_start((size_t)ev.data); - break ; - - case Q_AUDIO_STOP: - LOGFQUEUE("audio < Q_AUDIO_STOP"); - audio_stop_playback(); - break ; - - case Q_AUDIO_PAUSE: - LOGFQUEUE("audio < Q_AUDIO_PAUSE"); - pcmbuf_pause((bool)ev.data); - paused = (bool)ev.data; - break ; - - case Q_AUDIO_SKIP: - LOGFQUEUE("audio < Q_AUDIO_SKIP"); - audio_initiate_track_change((long)ev.data); - break; - - case Q_AUDIO_PRE_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_PRE_FF_REWIND"); - if (!playing) - break; - pcmbuf_pause(true); - break; - - case Q_AUDIO_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_FF_REWIND"); - if (!playing) - break ; - ci.seek_time = (long)ev.data+1; - break ; - - case Q_AUDIO_REBUFFER_SEEK: - LOGFQUEUE("audio < Q_AUDIO_REBUFFER_SEEK"); - audio_rebuffer_and_seek((size_t)ev.data); - break; - - case Q_AUDIO_CHECK_NEW_TRACK: - LOGFQUEUE("audio < Q_AUDIO_CHECK_NEW_TRACK"); - audio_check_new_track(); - break; - - case Q_AUDIO_DIR_SKIP: - LOGFQUEUE("audio < Q_AUDIO_DIR_SKIP"); - playlist_end = false; - if (global_settings.beep) - pcmbuf_beep(5000, 100, 2500*global_settings.beep); - audio_initiate_dir_change((long)ev.data); - break; - - case Q_AUDIO_NEW_PLAYLIST: - LOGFQUEUE("audio < Q_AUDIO_NEW_PLAYLIST"); - audio_new_playlist(); - break; - - case Q_AUDIO_FLUSH: - LOGFQUEUE("audio < Q_AUDIO_FLUSH"); - audio_invalidate_tracks(); - break ; - - case Q_AUDIO_TRACK_CHANGED: - LOGFQUEUE("audio < Q_AUDIO_TRACK_CHANGED"); - if (track_changed_callback) - track_changed_callback(&cur_ti->id3); - track_changed = true; - playlist_update_resume_info(audio_current_track()); - break ; - -#ifndef SIMULATOR - case SYS_USB_CONNECTED: - LOGFQUEUE("audio < SYS_USB_CONNECTED"); - audio_stop_playback(); - usb_acknowledge(SYS_USB_CONNECTED_ACK); - usb_wait_for_disconnect(&audio_queue); - break ; -#endif - - case SYS_TIMEOUT: - LOGFQUEUE("audio < SYS_TIMEOUT"); - break; - - default: - LOGFQUEUE("audio < default"); - } - } -} - -static void codec_thread(void) -{ - struct event ev; - int status; - size_t wrap; - - while (1) { - status = 0; - queue_wait(&codec_queue, &ev); - - switch (ev.id) { - case Q_CODEC_LOAD_DISK: - LOGFQUEUE("codec < Q_CODEC_LOAD_DISK"); - audio_codec_loaded = true; - if (voice_codec_loaded) - { - LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); - queue_post(&voice_codec_queue, Q_AUDIO_PLAY, 0); - } - mutex_lock(&mutex_codecthread); - current_codec = CODEC_IDX_AUDIO; - ci.stop_codec = false; - status = codec_load_file((const char *)ev.data, &ci); - mutex_unlock(&mutex_codecthread); - break ; - - case Q_CODEC_LOAD: - LOGFQUEUE("codec < Q_CODEC_LOAD"); - if (!cur_ti->has_codec) { - logf("Codec slot is empty!"); - /* Wait for the pcm buffer to go empty */ - while (pcm_is_playing()) - yield(); - /* This must be set to prevent an infinite loop */ - ci.stop_codec = true; - LOGFQUEUE("codec > codec Q_AUDIO_PLAY"); - queue_post(&codec_queue, Q_AUDIO_PLAY, 0); - break ; - } - - audio_codec_loaded = true; - if (voice_codec_loaded) - { - LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); - queue_post(&voice_codec_queue, Q_AUDIO_PLAY, 0); - } - mutex_lock(&mutex_codecthread); - current_codec = CODEC_IDX_AUDIO; - ci.stop_codec = false; - wrap = (size_t)&filebuf[filebuflen] - (size_t)cur_ti->codecbuf; - status = codec_load_ram(cur_ti->codecbuf, cur_ti->codecsize, - &filebuf[0], wrap, &ci); - mutex_unlock(&mutex_codecthread); - break ; - -#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; @@ -2686,495 +3112,9 @@ static void audio_reset_buffer(void) 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) +static void audio_test_track_changed_event(struct mp3entry *id3) { (void)id3; @@ -3182,7 +3122,7 @@ void test_track_changed_event(struct mp3entry *id3) } #endif -static void playback_init(void) +static void audio_playback_init(void) { static bool voicetagtrue = true; struct event ev; @@ -3196,14 +3136,14 @@ static void playback_init(void) #endif #ifdef ROCKBOX_HAS_LOGF - audio_set_track_changed_event(test_track_changed_event); + audio_set_track_changed_event(audio_test_track_changed_event); #endif /* Initialize codec api. */ ci.read_filebuf = codec_filebuf_callback; ci.pcmbuf_insert = codec_pcmbuf_insert_callback; ci.pcmbuf_insert_split = codec_pcmbuf_insert_split_callback; - ci.get_codec_memory = get_codec_memory_callback; + ci.get_codec_memory = codec_get_memory_callback; ci.request_buffer = codec_request_buffer_callback; ci.advance_buffer = codec_advance_buffer_callback; ci.advance_buffer_loc = codec_advance_buffer_loc_callback; @@ -3222,7 +3162,7 @@ static void playback_init(void) ci_voice.read_filebuf = voice_filebuf_callback; ci_voice.pcmbuf_insert = voice_pcmbuf_insert_callback; ci_voice.pcmbuf_insert_split = voice_pcmbuf_insert_split_callback; - ci_voice.get_codec_memory = get_voice_memory_callback; + ci_voice.get_codec_memory = voice_get_memory_callback; ci_voice.request_buffer = voice_request_buffer_callback; ci_voice.advance_buffer = voice_advance_buffer_callback; ci_voice.advance_buffer_loc = voice_advance_buffer_loc_callback; @@ -3266,39 +3206,120 @@ static void playback_init(void) sound_settings_apply(); } -void audio_preinit(void) +static void audio_thread(void) { - logf("playback system pre-init"); + struct event ev; - filebufused = 0; - filling = false; - current_codec = CODEC_IDX_AUDIO; - playing = false; - paused = false; - audio_codec_loaded = false; - voice_is_playing = false; - track_changed = false; - current_fd = -1; - track_buffer_callback = NULL; - track_unbuffer_callback = NULL; - track_changed_callback = NULL; - /* Just to prevent cur_ti never be anything random. */ - cur_ti = &tracks[0]; + /* At first initialize audio system in background. */ + audio_playback_init(); - mutex_init(&mutex_codecthread); + while (1) { + if (filling) + { + queue_wait_w_tmo(&audio_queue, &ev, 0); + if (ev.id == SYS_TIMEOUT) + ev.id = Q_AUDIO_FILL_BUFFER; + } + else + queue_wait_w_tmo(&audio_queue, &ev, HZ); - queue_init(&audio_queue); - queue_init(&codec_queue); - /* clear, not init to create a private queue */ - queue_clear(&codec_callback_queue); + switch (ev.id) { + case Q_AUDIO_FILL_BUFFER: + LOGFQUEUE("audio < Q_AUDIO_FILL_BUFFER"); + if (!filling) + if (!playing || playlist_end || ci.stop_codec) + break; + audio_fill_file_buffer(false, false, 0); + break; - create_thread(audio_thread, audio_stack, sizeof(audio_stack), - audio_thread_name); -} + case Q_AUDIO_PLAY: + LOGFQUEUE("audio < Q_AUDIO_PLAY"); + audio_clear_track_entries(true, false, true); + audio_play_start((size_t)ev.data); + break ; -void audio_init(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_POSTINIT"); - queue_post(&audio_queue, Q_AUDIO_POSTINIT, 0); + case Q_AUDIO_STOP: + LOGFQUEUE("audio < Q_AUDIO_STOP"); + audio_stop_playback(); + break ; + + case Q_AUDIO_PAUSE: + LOGFQUEUE("audio < Q_AUDIO_PAUSE"); + pcmbuf_pause((bool)ev.data); + paused = (bool)ev.data; + break ; + + case Q_AUDIO_SKIP: + LOGFQUEUE("audio < Q_AUDIO_SKIP"); + audio_initiate_track_change((long)ev.data); + break; + + case Q_AUDIO_PRE_FF_REWIND: + LOGFQUEUE("audio < Q_AUDIO_PRE_FF_REWIND"); + if (!playing) + break; + pcmbuf_pause(true); + break; + + case Q_AUDIO_FF_REWIND: + LOGFQUEUE("audio < Q_AUDIO_FF_REWIND"); + if (!playing) + break ; + ci.seek_time = (long)ev.data+1; + break ; + + case Q_AUDIO_REBUFFER_SEEK: + LOGFQUEUE("audio < Q_AUDIO_REBUFFER_SEEK"); + audio_rebuffer_and_seek((size_t)ev.data); + break; + + case Q_AUDIO_CHECK_NEW_TRACK: + LOGFQUEUE("audio < Q_AUDIO_CHECK_NEW_TRACK"); + audio_check_new_track(); + break; + + case Q_AUDIO_DIR_SKIP: + LOGFQUEUE("audio < Q_AUDIO_DIR_SKIP"); + playlist_end = false; + if (global_settings.beep) + pcmbuf_beep(5000, 100, 2500*global_settings.beep); + audio_initiate_dir_change((long)ev.data); + break; + + case Q_AUDIO_NEW_PLAYLIST: + LOGFQUEUE("audio < Q_AUDIO_NEW_PLAYLIST"); + audio_new_playlist(); + break; + + case Q_AUDIO_FLUSH: + LOGFQUEUE("audio < Q_AUDIO_FLUSH"); + audio_invalidate_tracks(); + break ; + + case Q_AUDIO_TRACK_CHANGED: + LOGFQUEUE("audio < Q_AUDIO_TRACK_CHANGED"); + if (track_changed_callback) + track_changed_callback(&cur_ti->id3); + track_changed = true; + playlist_update_resume_info(audio_current_track()); + break ; + +#ifndef SIMULATOR + case SYS_USB_CONNECTED: + LOGFQUEUE("audio < SYS_USB_CONNECTED"); + audio_stop_playback(); + usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_wait_for_disconnect(&audio_queue); + break ; +#endif + + case SYS_TIMEOUT: + LOGFQUEUE("audio < SYS_TIMEOUT"); + break; + + default: + LOGFQUEUE("audio < default"); + } + } } |