summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Bavin <pondlife@pondlife.me>2006-09-01 07:59:31 +0000
committerSteve Bavin <pondlife@pondlife.me>2006-09-01 07:59:31 +0000
commit3cc46e762065c1d7f6bceebaa39572e30ca78b2b (patch)
tree80fd48076d09c5c3f61eb59f2afe8e692a786acf
parent1bb8657a8f1a3145ae0d66068f142008012da1cb (diff)
downloadrockbox-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.c3453
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");
+ }
+ }
}