From 159c52dd36e5c008612458192904f57ea6dfdfad Mon Sep 17 00:00:00 2001 From: Miika Pekkarinen Date: Sat, 20 Aug 2005 11:13:19 +0000 Subject: [PATCH] Initial voice ui support for software codec platforms. Added also a beep when changing tracks. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7360 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 2 +- apps/codecs.c | 13 +- apps/codecs.h | 5 +- apps/codecs/wav.c | 13 +- apps/debug_menu.c | 10 +- apps/dsp.c | 195 +++++++------- apps/main.c | 3 + apps/pcmbuf.c | 104 ++++++++ apps/pcmbuf.h | 7 + apps/playback.c | 507 +++++++++++++++++++++++++++++-------- apps/playback.h | 1 + apps/playlist.c | 7 + apps/talk.c | 111 +++++++- apps/talk.h | 1 + firmware/mp3_playback.c | 16 +- firmware/sound.c | 7 - uisimulator/common/stubs.c | 2 + 17 files changed, 774 insertions(+), 230 deletions(-) diff --git a/apps/SOURCES b/apps/SOURCES index c1f3dfc88e..e8fd2d2ddb 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -22,7 +22,7 @@ settings_menu.c sleeptimer.c sound_menu.c status.c -#ifndef SIMULATOR +#if !defined(SIMULATOR) || CONFIG_HWCODEC == MASNONE talk.c #endif tree.c diff --git a/apps/codecs.c b/apps/codecs.c index b1d30868bc..004d4681f7 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -54,7 +54,7 @@ #ifdef SIMULATOR #if CONFIG_HWCODEC == MASNONE -static unsigned char codecbuf[CODEC_SIZE]; +unsigned char codecbuf[CODEC_SIZE]; #endif void *sim_codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap, int *pd); @@ -68,6 +68,8 @@ extern void* plugin_get_audio_buffer(int *buffer_size); static int codec_test(int api_version, int model, int memsize); +struct codec_api ci_voice; + struct codec_api ci = { CODEC_API_VERSION, codec_test, @@ -247,7 +249,8 @@ struct codec_api ci = { NULL, }; -int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap) +int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap, + struct codec_api *api) { enum codec_status (*codec_start)(const struct codec_api* api); int status; @@ -277,7 +280,7 @@ int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap) #endif /* SIMULATOR */ invalidate_icache(); - status = codec_start(&ci); + status = codec_start(api); #ifdef SIMULATOR sim_codec_close(pd); #endif @@ -285,7 +288,7 @@ int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap) return status; } -int codec_load_file(const char *plugin) +int codec_load_file(const char *plugin, struct codec_api *api) { char msgbuf[80]; int fd; @@ -309,7 +312,7 @@ int codec_load_file(const char *plugin) return CODEC_ERROR; } - return codec_load_ram(codecbuf, (size_t)rc, NULL, 0); + return codec_load_ram(codecbuf, (size_t)rc, NULL, 0, api); } static int codec_test(int api_version, int model, int memsize) diff --git a/apps/codecs.h b/apps/codecs.h index b2cf9e5a1e..3b8e1d8394 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -331,8 +331,9 @@ struct codec_api { /* defined by the codec loader (codec.c) */ #if CONFIG_HWCODEC == MASNONE -int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap); -int codec_load_file(const char* codec); +int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap, + struct codec_api *api); +int codec_load_file(const char* codec, struct codec_api *api); #endif /* defined by the codec */ diff --git a/apps/codecs/wav.c b/apps/codecs/wav.c index 527d33d286..d2ae7bd6c3 100644 --- a/apps/codecs/wav.c +++ b/apps/codecs/wav.c @@ -71,15 +71,12 @@ enum codec_status codec_start(struct codec_api* api) return CODEC_ERROR; } - while (!rb->taginfo_ready) + while (!*rb->taginfo_ready) rb->yield(); - - if (rb->id3->frequency != NATIVE_FREQUENCY) { - rb->configure(DSP_SET_FREQUENCY, (long *)(rb->id3->frequency)); - rb->configure(CODEC_DSP_ENABLE, (bool *)true); - } else { - rb->configure(CODEC_DSP_ENABLE, (bool *)false); - } + + /* Always enable DSP to support voice ui. */ + rb->configure(CODEC_DSP_ENABLE, (bool *)true); + rb->configure(DSP_SET_FREQUENCY, (long *)(rb->id3->frequency)); /* FIX: Correctly parse WAV header - we assume canonical 44-byte header */ diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 86e02a1e84..b4cd05699c 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -208,8 +208,8 @@ bool dbg_audio_thread(void) } #else extern size_t audiobuffer_free; -extern int codecbuflen; -extern int codecbufused; +extern int filebuflen; +extern int filebufused; extern int track_count; static int ticks, boost_ticks; @@ -260,12 +260,12 @@ bool dbg_audio_thread(void) bufsize-audiobuffer_free, HORIZONTAL); line++; - snprintf(buf, sizeof(buf), "codec: %d/%d", codecbufused, codecbuflen); + snprintf(buf, sizeof(buf), "codec: %d/%d", filebufused, filebuflen); lcd_puts(0, line++, buf); /* Playable space left */ - scrollbar(0, line*8, LCD_WIDTH, 6, codecbuflen, 0, - codecbufused, HORIZONTAL); + scrollbar(0, line*8, LCD_WIDTH, 6, filebuflen, 0, + filebufused, HORIZONTAL); line++; snprintf(buf, sizeof(buf), "track count: %d", track_count); diff --git a/apps/dsp.c b/apps/dsp.c index 2ba7fb99f6..8064a881a3 100644 --- a/apps/dsp.c +++ b/apps/dsp.c @@ -132,9 +132,12 @@ struct dither_data long random; }; -static struct dsp_config dsp IDATA_ATTR; +static struct dsp_config dsp_conf[2] IDATA_ATTR; static struct dither_data dither_data[2] IDATA_ATTR; -static struct resample_data resample_data[2] IDATA_ATTR; +static struct resample_data resample_data[2][2] IDATA_ATTR; + +extern int current_codec; +struct dsp_config *dsp; /* The internal format is 32-bit samples, non-interleaved, stereo. This * format is similar to the raw output from several codecs, so the amount @@ -155,20 +158,20 @@ static int convert_to_internal(char* src[], int count, long* dst[]) { count = MIN(SAMPLE_BUF_SIZE / 2, count); - if ((dsp.sample_depth <= NATIVE_DEPTH) - || (dsp.stereo_mode == STEREO_INTERLEAVED)) + if ((dsp->sample_depth <= NATIVE_DEPTH) + || (dsp->stereo_mode == STEREO_INTERLEAVED)) { dst[0] = &sample_buf[0]; - dst[1] = (dsp.stereo_mode == STEREO_MONO) + dst[1] = (dsp->stereo_mode == STEREO_MONO) ? dst[0] : &sample_buf[SAMPLE_BUF_SIZE / 2]; } else { dst[0] = (long*) src[0]; - dst[1] = (long*) ((dsp.stereo_mode == STEREO_MONO) ? src[0] : src[1]); + dst[1] = (long*) ((dsp->stereo_mode == STEREO_MONO) ? src[0] : src[1]); } - if (dsp.sample_depth <= NATIVE_DEPTH) + if (dsp->sample_depth <= NATIVE_DEPTH) { short* s0 = (short*) src[0]; long* d0 = dst[0]; @@ -176,7 +179,7 @@ static int convert_to_internal(char* src[], int count, long* dst[]) int scale = WORD_SHIFT; int i; - if (dsp.stereo_mode == STEREO_INTERLEAVED) + if (dsp->stereo_mode == STEREO_INTERLEAVED) { for (i = 0; i < count; i++) { @@ -184,7 +187,7 @@ static int convert_to_internal(char* src[], int count, long* dst[]) *d1++ = *s0++ << scale; } } - else if (dsp.stereo_mode == STEREO_NONINTERLEAVED) + else if (dsp->stereo_mode == STEREO_NONINTERLEAVED) { short* s1 = (short*) src[1]; @@ -202,7 +205,7 @@ static int convert_to_internal(char* src[], int count, long* dst[]) } } } - else if (dsp.stereo_mode == STEREO_INTERLEAVED) + else if (dsp->stereo_mode == STEREO_INTERLEAVED) { long* s0 = (long*) src[0]; long* d0 = dst[0]; @@ -216,18 +219,18 @@ static int convert_to_internal(char* src[], int count, long* dst[]) } } - if (dsp.stereo_mode == STEREO_NONINTERLEAVED) + if (dsp->stereo_mode == STEREO_NONINTERLEAVED) { - src[0] += count * dsp.sample_bytes; - src[1] += count * dsp.sample_bytes; + src[0] += count * dsp->sample_bytes; + src[1] += count * dsp->sample_bytes; } - else if (dsp.stereo_mode == STEREO_INTERLEAVED) + else if (dsp->stereo_mode == STEREO_INTERLEAVED) { - src[0] += count * dsp.sample_bytes * 2; + src[0] += count * dsp->sample_bytes * 2; } else { - src[0] += count * dsp.sample_bytes; + src[0] += count * dsp->sample_bytes; } return count; @@ -310,29 +313,33 @@ static inline int resample(long* src[], int count) { long new_count; - if (dsp.frequency != NATIVE_FREQUENCY) + if (dsp->frequency != NATIVE_FREQUENCY) { long* d0 = &resample_buf[0]; /* Only process the second channel if needed. */ long* d1 = (src[0] == src[1]) ? d0 : &resample_buf[RESAMPLE_BUF_SIZE / 2]; - if (dsp.frequency < NATIVE_FREQUENCY) + if (dsp->frequency < NATIVE_FREQUENCY) { - new_count = upsample(d0, src[0], count, &resample_data[0]); + new_count = upsample(d0, src[0], count, + &resample_data[current_codec][0]); if (d0 != d1) { - upsample(d1, src[1], count, &resample_data[1]); + upsample(d1, src[1], count, + &resample_data[current_codec][1]); } } else { - new_count = downsample(d0, src[0], count, &resample_data[0]); + new_count = downsample(d0, src[0], count, + &resample_data[current_codec][0]); if (d0 != d1) { - downsample(d1, src[1], count, &resample_data[1]); + downsample(d1, src[1], count, + &resample_data[current_codec][1]); } } @@ -389,8 +396,8 @@ static long dither_sample(long sample, long bias, long mask, /* Clip and quantize */ - min = dsp.clip_min; - max = dsp.clip_max; + min = dsp->clip_min; + max = dsp->clip_max; sample = clip_sample(sample, min, max); output = clip_sample(output, min, max) & ~mask; @@ -407,13 +414,13 @@ static long dither_sample(long sample, long bias, long mask, */ static void apply_gain(long* src[], int count) { - if (dsp.replaygain) + if (dsp->replaygain) { long* s0 = src[0]; long* s1 = src[1]; long* d0 = &sample_buf[0]; long* d1 = (s0 == s1) ? d0 : &sample_buf[SAMPLE_BUF_SIZE / 2]; - long gain = dsp.replaygain; + long gain = dsp->replaygain; long s; long i; @@ -442,11 +449,11 @@ static void write_samples(short* dst, long* src[], int count) { long* s0 = src[0]; long* s1 = src[1]; - int scale = dsp.frac_bits + 1 - NATIVE_DEPTH; + int scale = dsp->frac_bits + 1 - NATIVE_DEPTH; - if (dsp.dither_enabled) + if (dsp->dither_enabled) { - long bias = (1L << (dsp.frac_bits - NATIVE_DEPTH)); + long bias = (1L << (dsp->frac_bits - NATIVE_DEPTH)); long mask = (1L << scale) - 1; while (count-- > 0) @@ -459,8 +466,8 @@ static void write_samples(short* dst, long* src[], int count) } else { - long min = dsp.clip_min; - long max = dsp.clip_max; + long min = dsp->clip_min; + long max = dsp->clip_max; while (count-- > 0) { @@ -482,10 +489,13 @@ long dsp_process(char* dst, char* src[], long size) { long* tmp[2]; long written = 0; - long factor = (dsp.stereo_mode != STEREO_MONO) ? 2 : 1; + long factor; int samples; - size /= dsp.sample_bytes * factor; + dsp = &dsp_conf[current_codec]; + + factor = (dsp->stereo_mode != STEREO_MONO) ? 2 : 1; + size /= dsp->sample_bytes * factor; INIT(); dsp_set_replaygain(false); @@ -513,21 +523,23 @@ long dsp_process(char* dst, char* src[], long size) /* dsp_input_size MUST be called afterwards */ long dsp_output_size(long size) { - if (dsp.sample_depth > NATIVE_DEPTH) + dsp = &dsp_conf[current_codec]; + + if (dsp->sample_depth > NATIVE_DEPTH) { size /= 2; } - if (dsp.frequency != NATIVE_FREQUENCY) + if (dsp->frequency != NATIVE_FREQUENCY) { size = (long) ((((unsigned long) size * NATIVE_FREQUENCY) - + (dsp.frequency - 1)) / dsp.frequency); + + (dsp->frequency - 1)) / dsp->frequency); } /* round to the next multiple of 2 (these are shorts) */ size = (size + 1) & ~1; - if (dsp.stereo_mode == STEREO_MONO) + if (dsp->stereo_mode == STEREO_MONO) { size *= 2; } @@ -547,25 +559,28 @@ long dsp_output_size(long size) */ long dsp_input_size(long size) { + dsp = &dsp_conf[current_codec]; + /* convert to number of output stereo samples. */ size /= 2; /* Mono means we need half input samples to fill the output buffer */ - if (dsp.stereo_mode == STEREO_MONO) + if (dsp->stereo_mode == STEREO_MONO) size /= 2; /* size is now the number of resampled input samples. Convert to original input samples. */ - if (dsp.frequency != NATIVE_FREQUENCY) + if (dsp->frequency != NATIVE_FREQUENCY) { /* Use the real resampling delta = - * (unsigned long) dsp.frequency * 65536 / NATIVE_FREQUENCY, and + * (unsigned long) dsp->frequency * 65536 / NATIVE_FREQUENCY, and * round towards zero to avoid buffer overflows. */ - size = ((unsigned long)size * resample_data[0].delta) >> 16; + size = ((unsigned long)size * + resample_data[current_codec][0].delta) >> 16; } /* Convert back to bytes. */ - if (dsp.sample_depth > NATIVE_DEPTH) + if (dsp->sample_depth > NATIVE_DEPTH) size *= 4; else size *= 2; @@ -575,90 +590,96 @@ long dsp_input_size(long size) int dsp_stereo_mode(void) { - return dsp.stereo_mode; + dsp = &dsp_conf[current_codec]; + + return dsp->stereo_mode; } bool dsp_configure(int setting, void *value) { + dsp = &dsp_conf[current_codec]; + switch (setting) { case DSP_SET_FREQUENCY: - memset(resample_data, 0, sizeof(resample_data)); + memset(&resample_data[current_codec][0], 0, + sizeof(struct resample_data) * 2); /* Fall through!!! */ case DSP_SWITCH_FREQUENCY: - dsp.frequency = ((int) value == 0) ? NATIVE_FREQUENCY : (int) value; - resample_data[0].delta = resample_data[1].delta = - (unsigned long) dsp.frequency * 65536 / NATIVE_FREQUENCY; + dsp->frequency = ((int) value == 0) ? NATIVE_FREQUENCY : (int) value; + resample_data[current_codec][0].delta = + resample_data[current_codec][1].delta = + (unsigned long) dsp->frequency * 65536 / NATIVE_FREQUENCY; break; case DSP_SET_CLIP_MIN: - dsp.clip_min = (long) value; + dsp->clip_min = (long) value; break; case DSP_SET_CLIP_MAX: - dsp.clip_max = (long) value; + dsp->clip_max = (long) value; break; case DSP_SET_SAMPLE_DEPTH: - dsp.sample_depth = (long) value; + dsp->sample_depth = (long) value; - if (dsp.sample_depth <= NATIVE_DEPTH) + if (dsp->sample_depth <= NATIVE_DEPTH) { - dsp.frac_bits = WORD_FRACBITS; - dsp.sample_bytes = sizeof(short); - dsp.clip_max = ((1 << WORD_FRACBITS) - 1); - dsp.clip_min = -((1 << WORD_FRACBITS)); + dsp->frac_bits = WORD_FRACBITS; + dsp->sample_bytes = sizeof(short); + dsp->clip_max = ((1 << WORD_FRACBITS) - 1); + dsp->clip_min = -((1 << WORD_FRACBITS)); } else { - dsp.frac_bits = (long) value; - dsp.sample_bytes = sizeof(long); + dsp->frac_bits = (long) value; + dsp->sample_bytes = sizeof(long); } break; case DSP_SET_STEREO_MODE: - dsp.stereo_mode = (long) value; + dsp->stereo_mode = (long) value; break; case DSP_RESET: - dsp.dither_enabled = false; - dsp.stereo_mode = STEREO_NONINTERLEAVED; - dsp.clip_max = ((1 << WORD_FRACBITS) - 1); - dsp.clip_min = -((1 << WORD_FRACBITS)); - dsp.track_gain = 0; - dsp.album_gain = 0; - dsp.track_peak = 0; - dsp.album_peak = 0; - dsp.frequency = NATIVE_FREQUENCY; - dsp.sample_depth = NATIVE_DEPTH; - dsp.frac_bits = WORD_FRACBITS; - dsp.new_gain = true; + dsp->dither_enabled = false; + dsp->stereo_mode = STEREO_NONINTERLEAVED; + dsp->clip_max = ((1 << WORD_FRACBITS) - 1); + dsp->clip_min = -((1 << WORD_FRACBITS)); + dsp->track_gain = 0; + dsp->album_gain = 0; + dsp->track_peak = 0; + dsp->album_peak = 0; + dsp->frequency = NATIVE_FREQUENCY; + dsp->sample_depth = NATIVE_DEPTH; + dsp->frac_bits = WORD_FRACBITS; + dsp->new_gain = true; break; case DSP_DITHER: memset(dither_data, 0, sizeof(dither_data)); - dsp.dither_enabled = (bool) value; + dsp->dither_enabled = (bool) value; break; case DSP_SET_TRACK_GAIN: - dsp.track_gain = (long) value; - dsp.new_gain = true; + dsp->track_gain = (long) value; + dsp->new_gain = true; break; case DSP_SET_ALBUM_GAIN: - dsp.album_gain = (long) value; - dsp.new_gain = true; + dsp->album_gain = (long) value; + dsp->new_gain = true; break; case DSP_SET_TRACK_PEAK: - dsp.track_peak = (long) value; - dsp.new_gain = true; + dsp->track_peak = (long) value; + dsp->new_gain = true; break; case DSP_SET_ALBUM_PEAK: - dsp.album_peak = (long) value; - dsp.new_gain = true; + dsp->album_peak = (long) value; + dsp->new_gain = true; break; default: @@ -670,11 +691,13 @@ bool dsp_configure(int setting, void *value) void dsp_set_replaygain(bool always) { - if (always || dsp.new_gain) + dsp = &dsp_conf[current_codec]; + + if (always || dsp->new_gain) { long gain = 0; - dsp.new_gain = false; + dsp->new_gain = false; if (global_settings.replaygain || global_settings.replaygain_noclip) { @@ -682,8 +705,8 @@ void dsp_set_replaygain(bool always) if (global_settings.replaygain) { - gain = (global_settings.replaygain_track || !dsp.album_gain) - ? dsp.track_gain : dsp.album_gain; + gain = (global_settings.replaygain_track || !dsp->album_gain) + ? dsp->track_gain : dsp->album_gain; if (global_settings.replaygain_preamp) { @@ -694,8 +717,8 @@ void dsp_set_replaygain(bool always) } } - peak = (global_settings.replaygain_track || !dsp.album_peak) - ? dsp.track_peak : dsp.album_peak; + peak = (global_settings.replaygain_track || !dsp->album_peak) + ? dsp->track_peak : dsp->album_peak; if (gain == 0) { @@ -718,6 +741,6 @@ void dsp_set_replaygain(bool always) } /* Store in S8.23 format to simplify calculations. */ - dsp.replaygain = gain >> 1; + dsp->replaygain = gain >> 1; } } diff --git a/apps/main.c b/apps/main.c index 90be703c6b..55897de5f5 100644 --- a/apps/main.c +++ b/apps/main.c @@ -129,6 +129,9 @@ void init(void) global_settings.mdb_enable, global_settings.superbass); button_clear_queue(); /* Empty the keyboard buffer */ +#if CONFIG_HWCODEC == MASNONE + talk_init(); +#endif } #else diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c index 691f8d5a19..5f78901a56 100644 --- a/apps/pcmbuf.c +++ b/apps/pcmbuf.c @@ -34,6 +34,7 @@ #include "buffer.h" #include "settings.h" #include "audio.h" +#include "dsp.h" #define CHUNK_SIZE PCMBUF_GUARD /* Must be a power of 2 */ @@ -86,9 +87,11 @@ struct pcmbufdesc volatile int pcmbuf_read_index; volatile int pcmbuf_write_index; int pcmbuf_unplayed_bytes; +int pcmbuf_mix_used_bytes; int pcmbuf_watermark; void (*pcmbuf_watermark_event)(int bytes_left); static int last_chunksize; +static long mixpos = 0; static void pcmbuf_boost(bool state) { @@ -173,6 +176,7 @@ bool pcmbuf_add_chunk(void *addr, int size, void (*callback)(void)) pcmbuffers[pcmbuf_write_index].callback = callback; pcmbuf_write_index = (pcmbuf_write_index+1) & NUM_PCM_BUFFERS_MASK; pcmbuf_unplayed_bytes += size; + pcmbuf_mix_used_bytes = MAX(0, pcmbuf_mix_used_bytes - size); return true; } else @@ -254,6 +258,7 @@ void pcmbuf_play_stop(void) pcm_play_stop(); last_chunksize = 0; pcmbuf_unplayed_bytes = 0; + pcmbuf_mix_used_bytes = 0; pcmbuf_read_index = 0; pcmbuf_write_index = 0; audiobuffer_pos = 0; @@ -297,6 +302,13 @@ void pcmbuf_flush_audio(void) crossfade_init = true; } +/* Force playback. */ +void pcmbuf_play_start(void) +{ + if (!pcm_is_playing() && pcmbuf_unplayed_bytes) + pcm_play_data(pcmbuf_callback); +} + void pcmbuf_flush_fillpos(void) { int copy_n; @@ -562,6 +574,98 @@ bool pcmbuf_insert_buffer(char *buf, long length) return true; } +/* Generates a constant square wave sound with a given frequency + in Hertz for a duration in milliseconds. */ +void pcmbuf_beep(int frequency, int duration, int amplitude) +{ + int state = 0, count = 0; + int interval = NATIVE_FREQUENCY / frequency; + int pos; + short *buf = (short *)audiobuffer; + int bufsize = pcmbuf_size / 2; + + /* FIXME: Should start playback. */ + //if (pcmbuf_unplayed_bytes * 1000 < 4 * NATIVE_FREQUENCY * duration) + // return ; + + pos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2; + if (pos < 0) + pos += bufsize; + + duration = NATIVE_FREQUENCY / 1000 * duration; + while (duration-- > 0) + { + if (state) { + buf[pos] = MIN(MAX(buf[pos] + amplitude, -32768), 32767); + if (++pos >= bufsize) + pos = 0; + buf[pos] = MIN(MAX(buf[pos] + amplitude, -32768), 32767); + } else { + buf[pos] = MIN(MAX(buf[pos] - amplitude, -32768), 32767); + if (++pos >= bufsize) + pos = 0; + buf[pos] = MIN(MAX(buf[pos] - amplitude, -32768), 32767); + } + + if (++count >= interval) + { + count = 0; + if (state) + state = 0; + else + state = 1; + } + pos++; + if (pos >= bufsize) + pos = 0; + } +} + +/* Returns pcm buffer usage in percents (0 to 100). */ +int pcmbuf_usage(void) +{ + return pcmbuf_unplayed_bytes * 100 / pcmbuf_size; +} + +int pcmbuf_mix_usage(void) +{ + return pcmbuf_mix_used_bytes * 100 / pcmbuf_unplayed_bytes; +} + +void pcmbuf_reset_mixpos(void) +{ + int bufsize = pcmbuf_size / 2; + + pcmbuf_mix_used_bytes = 0; + mixpos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2; + if (mixpos < 0) + mixpos += bufsize; + if (mixpos >= bufsize) + mixpos -= bufsize; +} + +void pcmbuf_mix(char *buf, long length) +{ + short *ibuf = (short *)buf; + short *obuf = (short *)audiobuffer; + int bufsize = pcmbuf_size / 2; + + if (pcmbuf_mix_used_bytes == 0) + pcmbuf_reset_mixpos(); + + pcmbuf_mix_used_bytes += length; + length /= 2; + + while (length-- > 0) { + obuf[mixpos] = MIN(MAX(obuf[mixpos] + *ibuf*4, -32768), 32767); + + ibuf++; + mixpos++; + if (mixpos >= bufsize) + mixpos = 0; + } +} + void pcmbuf_crossfade_enable(bool on_off) { crossfade_enabled = on_off; diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h index 6381dbc27e..f2533defe6 100644 --- a/apps/pcmbuf.h +++ b/apps/pcmbuf.h @@ -36,6 +36,7 @@ void pcmbuf_set_watermark(int numbytes, void (*callback)(int bytes_left)); void pcmbuf_set_boost_mode(bool state); bool pcmbuf_is_lowdata(void); void pcmbuf_flush_audio(void); +void pcmbuf_play_start(void); bool pcmbuf_crossfade_init(int type); void pcmbuf_add_event(void (*event_handler)(void)); unsigned int pcmbuf_get_latency(void); @@ -45,4 +46,10 @@ void* pcmbuf_request_buffer(long length, long *realsize); bool pcmbuf_is_crossfade_enabled(void); void pcmbuf_crossfade_enable(bool on_off); +int pcmbuf_usage(void); +int pcmbuf_mix_usage(void); +void pcmbuf_beep(int frequency, int duration, int amplitude); +void pcmbuf_reset_mixpos(void); +void pcmbuf_mix(char *buf, long length); + #endif diff --git a/apps/playback.c b/apps/playback.c index e12b01ee55..8829757949 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -60,18 +60,20 @@ #include "misc.h" #include "sound.h" #include "metadata.h" +#include "talk.h" -static volatile bool codec_loaded; +static volatile bool audio_codec_loaded; +static volatile bool voice_codec_loaded; static volatile bool playing; static volatile bool paused; -#define CODEC_VORBIS "/.rockbox/codecs/vorbis.codec"; -#define CODEC_MPA_L3 "/.rockbox/codecs/mpa.codec"; -#define CODEC_FLAC "/.rockbox/codecs/flac.codec"; -#define CODEC_WAV "/.rockbox/codecs/wav.codec"; -#define CODEC_A52 "/.rockbox/codecs/a52.codec"; -#define CODEC_MPC "/.rockbox/codecs/mpc.codec"; -#define CODEC_WAVPACK "/.rockbox/codecs/wavpack.codec"; +#define CODEC_VORBIS "/.rockbox/codecs/vorbis.codec" +#define CODEC_MPA_L3 "/.rockbox/codecs/mpa.codec" +#define CODEC_FLAC "/.rockbox/codecs/flac.codec" +#define CODEC_WAV "/.rockbox/codecs/wav.codec" +#define CODEC_A52 "/.rockbox/codecs/a52.codec" +#define CODEC_MPC "/.rockbox/codecs/mpc.codec" +#define CODEC_WAVPACK "/.rockbox/codecs/wavpack.codec" #define AUDIO_FILL_CYCLE (1024*256) #define AUDIO_DEFAULT_WATERMARK (1024*512) @@ -96,6 +98,10 @@ static volatile bool paused; #define MALLOC_BUFSIZE (512*1024) #define GUARD_BUFSIZE (8*1024) +/* As defined in plugin.lds */ +#define CODEC_IRAM_ORIGIN 0x10010000 +#define CODEC_IRAM_SIZE 0x8000 + extern bool audio_is_initialized; /* Buffer control thread. */ @@ -108,19 +114,39 @@ static struct event_queue codec_queue; static long codec_stack[(DEFAULT_STACK_SIZE + 0x2500)/sizeof(long)] IDATA_ATTR; static const char codec_thread_name[] = "codec"; +/* Voice codec thread. */ +static struct event_queue voice_codec_queue; +/* Not enough IRAM for this. */ +static long voice_codec_stack[(DEFAULT_STACK_SIZE + 0x2500)/sizeof(long)]; +static const char voice_codec_thread_name[] = "voice codec"; + static struct mutex mutex_bufferfill; +static struct mutex mutex_codecthread; + +static struct mp3entry id3_voice; + +#define CODEC_IDX_AUDIO 0 +#define CODEC_IDX_VOICE 1 + +static char *voicebuf; +static int voice_remaining; +static bool voice_is_playing; +static void (*voice_getmore)(unsigned char** start, int* size); /* Is file buffer currently being refilled? */ static volatile bool filling; +volatile int current_codec; +extern unsigned char codecbuf[]; + /* Ring buffer where tracks and codecs are loaded. */ -static char *codecbuf; +static char *filebuf; /* Total size of the ring buffer. */ -int codecbuflen; +int filebuflen; /* Bytes available in the buffer. */ -int codecbufused; +int filebufused; /* Ring buffer read and write indexes. */ static volatile int buf_ridx; @@ -153,6 +179,7 @@ static struct track_info *cur_ti; /* Codec API including function callbacks. */ extern struct codec_api ci; +extern struct codec_api ci_voice; /* When we change a song and buffer is not in filling state, this variable keeps information about whether to go a next/previous track. */ @@ -174,6 +201,59 @@ static bool v1first = false; static void mp3_set_elapsed(struct mp3entry* id3); int mp3_get_file_pos(void); +static void do_swap(int idx_old, int idx_new) +{ +#ifndef SIMULATOR + unsigned char *iram_p = (unsigned char *)(CODEC_IRAM_ORIGIN); + unsigned char *iram_buf[2]; +#endif + unsigned char *dram_buf[2]; + + +#ifndef SIMULATOR + iram_buf[0] = &filebuf[filebuflen]; + iram_buf[1] = &filebuf[filebuflen+CODEC_IRAM_SIZE]; + memcpy(iram_buf[idx_old], iram_p, CODEC_IRAM_SIZE); + memcpy(iram_p, iram_buf[idx_new], CODEC_IRAM_SIZE); +#endif + + dram_buf[0] = &filebuf[filebuflen+CODEC_IRAM_SIZE*2]; + dram_buf[1] = &filebuf[filebuflen+CODEC_IRAM_SIZE*2+CODEC_SIZE]; + memcpy(dram_buf[idx_old], codecbuf, CODEC_SIZE); + memcpy(codecbuf, dram_buf[idx_new], CODEC_SIZE); +} + +static void swap_codec(void) +{ + int last_codec; + + logf("swapping codec:%d", current_codec); + + /* We should swap codecs' IRAM contents and code space. */ + do_swap(current_codec, !current_codec); + + last_codec = current_codec; + current_codec = !current_codec; + + /* Release the semaphore and force a task switch. */ + mutex_unlock(&mutex_codecthread); + sleep(1); + + /* Waiting until we are ready to run again. */ + mutex_lock(&mutex_codecthread); + + /* Check if codec swap did not happen. */ + if (current_codec != last_codec) + { + logf("no codec switch happened!"); + do_swap(current_codec, !current_codec); + current_codec = !current_codec; + } + + invalidate_icache(); + logf("codec resuming:%d", current_codec); +} + bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2, long length) { @@ -209,12 +289,41 @@ bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2, pcmbuf_flush_buffer(0); DEBUGF("Warning: dsp_input_size(%ld=dsp_output_size(%ld))=%ld <= 0\n", output_size, length, input_size); - /* should we really continue, or should we break? */ + /* should we really continue, or should we break? + * We should probably continue because calling pcmbuf_flush_buffer(0) + * will wrap the buffer if it was fully filled and so next call to + * pcmbuf_request_buffer should give the requested output_size. */ continue; } output_size = dsp_process(dest, src, input_size); - pcmbuf_flush_buffer(output_size); + + /* Hotswap between audio and voice codecs as necessary. */ + switch (current_codec) + { + case CODEC_IDX_AUDIO: + pcmbuf_flush_buffer(output_size); + if (voice_is_playing && pcmbuf_usage() > 30 + && pcmbuf_mix_usage() < 20) + { + cpu_boost(true); + swap_codec(); + cpu_boost(false); + } + break ; + + case CODEC_IDX_VOICE: + if (audio_codec_loaded) { + pcmbuf_mix(dest, output_size); + if ((pcmbuf_usage() < 10) + || pcmbuf_mix_usage() > 70) + swap_codec(); + } else { + pcmbuf_flush_buffer(output_size); + } + break ; + } + length -= input_size; } @@ -241,6 +350,9 @@ bool codec_pcmbuf_insert_callback(char *buf, long length) void* get_codec_memory_callback(long *size) { *size = MALLOC_BUFSIZE; + if (voice_codec_loaded) + return &audiobuf[talk_get_bufsize()]; + return &audiobuf[0]; } @@ -248,7 +360,7 @@ void codec_set_elapsed_callback(unsigned int value) { unsigned int latency; - if (ci.stop_codec) + if (ci.stop_codec || current_codec == CODEC_IDX_VOICE) return ; latency = pcmbuf_get_latency(); @@ -265,7 +377,7 @@ void codec_set_offset_callback(unsigned int value) { unsigned int latency; - if (ci.stop_codec) + if (ci.stop_codec || current_codec == CODEC_IDX_VOICE) return ; latency = pcmbuf_get_latency() * cur_ti->id3.bitrate / 8; @@ -283,7 +395,7 @@ long codec_filebuf_callback(void *ptr, long size) int copy_n; int part_n; - if (ci.stop_codec || !playing) + if (ci.stop_codec || !playing || current_codec == CODEC_IDX_VOICE) return 0; copy_n = MIN((off_t)size, (off_t)cur_ti->available + cur_ti->filerem); @@ -297,26 +409,78 @@ long codec_filebuf_callback(void *ptr, long size) if (copy_n == 0) return 0; - part_n = MIN(copy_n, codecbuflen - buf_ridx); - memcpy(buf, &codecbuf[buf_ridx], part_n); + part_n = MIN(copy_n, filebuflen - buf_ridx); + memcpy(buf, &filebuf[buf_ridx], part_n); if (part_n < copy_n) { - memcpy(&buf[part_n], &codecbuf[0], copy_n - part_n); + memcpy(&buf[part_n], &filebuf[0], copy_n - part_n); } buf_ridx += copy_n; - if (buf_ridx >= codecbuflen) - buf_ridx -= codecbuflen; + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; ci.curpos += copy_n; cur_ti->available -= copy_n; - codecbufused -= copy_n; + filebufused -= copy_n; return copy_n; } +void* voice_request_data(long *realsize, long reqsize) +{ + while (queue_empty(&voice_codec_queue) && (voice_remaining == 0 + || voicebuf == NULL) && !ci_voice.stop_codec) + { + yield(); + if (audio_codec_loaded && (pcmbuf_usage() < 30 + || !voice_is_playing || voicebuf == NULL)) + { + swap_codec(); + } + if (!voice_is_playing) + sleep(HZ/16); + + if (voice_remaining) + { + voice_is_playing = true; + break ; + } + + if (voice_getmore != NULL) + { + voice_getmore((unsigned char **)&voicebuf, (int *)&voice_remaining); + + if (!voice_remaining) + { + voice_is_playing = false; + /* Force pcm playback. */ + pcmbuf_play_start(); + } + } + } + + if (reqsize < 0) + reqsize = 0; + + voice_is_playing = true; + *realsize = voice_remaining; + if (*realsize > reqsize) + *realsize = reqsize; + + if (*realsize == 0) + return NULL; + + return voicebuf; +} + void* codec_request_buffer_callback(long *realsize, long reqsize) { long part_n; - + + /* Voice codec. */ + if (current_codec == CODEC_IDX_VOICE) { + return voice_request_data(realsize, reqsize); + } + if (ci.stop_codec || !playing) { *realsize = 0; return NULL; @@ -335,22 +499,22 @@ void* codec_request_buffer_callback(long *realsize, long reqsize) } } - part_n = MIN((int)*realsize, codecbuflen - buf_ridx); + part_n = MIN((int)*realsize, filebuflen - buf_ridx); if (part_n < *realsize) { part_n += GUARD_BUFSIZE; if (part_n < *realsize) *realsize = part_n; - memcpy(&codecbuf[codecbuflen], &codecbuf[0], *realsize - - (codecbuflen - buf_ridx)); + memcpy(&filebuf[filebuflen], &filebuf[0], *realsize - + (filebuflen - buf_ridx)); } - return (char *)&codecbuf[buf_ridx]; + return (char *)&filebuf[buf_ridx]; } static bool rebuffer_and_seek(int newpos) { int fd; - + logf("Re-buffering song"); mutex_lock(&mutex_bufferfill); @@ -367,7 +531,7 @@ static bool rebuffer_and_seek(int newpos) /* Clear codec buffer. */ audio_invalidate_tracks(); - codecbufused = 0; + filebufused = 0; buf_ridx = buf_widx = 0; cur_ti->filerem = cur_ti->filesize - newpos; cur_ti->filepos = newpos; @@ -390,6 +554,15 @@ static bool rebuffer_and_seek(int newpos) void codec_advance_buffer_callback(long amount) { + if (current_codec == CODEC_IDX_VOICE) { + //logf("voice ad.buf:%d", amount); + amount = MAX(0, MIN(amount, voice_remaining)); + voicebuf += amount; + voice_remaining -= amount; + + return ; + } + if (amount > cur_ti->available + cur_ti->filerem) amount = cur_ti->available + cur_ti->filerem; @@ -400,10 +573,10 @@ void codec_advance_buffer_callback(long amount) } buf_ridx += amount; - if (buf_ridx >= codecbuflen) - buf_ridx -= codecbuflen; + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; cur_ti->available -= amount; - codecbufused -= amount; + filebufused -= amount; ci.curpos += amount; codec_set_offset_callback(ci.curpos); } @@ -411,15 +584,18 @@ void codec_advance_buffer_callback(long amount) void codec_advance_buffer_loc_callback(void *ptr) { long amount; - - amount = (int)ptr - (int)&codecbuf[buf_ridx]; + + if (current_codec == CODEC_IDX_VOICE) + amount = (int)ptr - (int)voicebuf; + else + amount = (int)ptr - (int)&filebuf[buf_ridx]; codec_advance_buffer_callback(amount); } off_t codec_mp3_get_filepos_callback(int newtime) { off_t newpos; - + cur_ti->id3.elapsed = newtime; newpos = mp3_get_file_pos(); @@ -429,7 +605,10 @@ off_t codec_mp3_get_filepos_callback(int newtime) bool codec_seek_buffer_callback(off_t newpos) { int difference; - + + if (current_codec == CODEC_IDX_VOICE) + return false; + if (newpos < 0) newpos = 0; @@ -457,11 +636,11 @@ bool codec_seek_buffer_callback(off_t newpos) /* Seeking inside buffer space. */ logf("seek: -%d", difference); - codecbufused += difference; + filebufused += difference; cur_ti->available += difference; buf_ridx -= difference; if (buf_ridx < 0) - buf_ridx = codecbuflen + buf_ridx; + buf_ridx = filebuflen + buf_ridx; ci.curpos -= difference; if (!pcmbuf_is_crossfade_active()) pcmbuf_play_stop(); @@ -473,8 +652,11 @@ static void set_filebuf_watermark(int seconds) { long bytes; + if (current_codec == CODEC_IDX_VOICE) + return ; + bytes = MAX((int)cur_ti->id3.bitrate * seconds * (1000/8), conf_watermark); - bytes = MIN(bytes, codecbuflen / 2); + bytes = MIN(bytes, filebuflen / 2); conf_watermark = bytes; } @@ -540,7 +722,7 @@ void yield_codecs(void) sleep(5); while ((pcmbuf_is_crossfade_active() || pcmbuf_is_lowdata()) && !ci.stop_codec && playing && queue_empty(&audio_queue) - && codecbufused > (128*1024)) + && filebufused > (128*1024)) yield(); } @@ -552,18 +734,18 @@ void strip_id3v1_tag(void) int tagptr; bool found = true; - if (codecbufused >= 128) + if (filebufused >= 128) { tagptr = buf_widx - 128; if (tagptr < 0) - tagptr += codecbuflen; + tagptr += filebuflen; for(i = 0;i < 3;i++) { - if(tagptr >= codecbuflen) - tagptr -= codecbuflen; + if(tagptr >= filebuflen) + tagptr -= filebuflen; - if(codecbuf[tagptr] != tag[i]) + if(filebuf[tagptr] != tag[i]) { found = false; break; @@ -578,7 +760,7 @@ void strip_id3v1_tag(void) logf("Skipping ID3v1 tag\n"); buf_widx -= 128; tracks[track_widx].available -= 128; - codecbufused -= 128; + filebufused -= 128; } } } @@ -603,9 +785,9 @@ void audio_fill_file_buffer(void) if (fill_bytesleft == 0) break ; - rc = MIN(conf_filechunk, codecbuflen - buf_widx); + rc = MIN(conf_filechunk, filebuflen - buf_widx); rc = MIN(rc, fill_bytesleft); - rc = read(current_fd, &codecbuf[buf_widx], rc); + rc = read(current_fd, &filebuf[buf_widx], rc); if (rc <= 0) { tracks[track_widx].filerem = 0; strip_id3v1_tag(); @@ -613,13 +795,13 @@ void audio_fill_file_buffer(void) } buf_widx += rc; - if (buf_widx >= codecbuflen) - buf_widx -= codecbuflen; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; i += rc; tracks[track_widx].available += rc; tracks[track_widx].filerem -= rc; tracks[track_widx].filepos += rc; - codecbufused += rc; + filebufused += rc; fill_bytesleft -= rc; } @@ -725,15 +907,15 @@ bool loadcodec(const char *trackname, bool start_play) while (i < size) { yield_codecs(); - copy_n = MIN(conf_filechunk, codecbuflen - buf_widx); - rc = read(fd, &codecbuf[buf_widx], copy_n); + copy_n = MIN(conf_filechunk, filebuflen - buf_widx); + rc = read(fd, &filebuf[buf_widx], copy_n); if (rc < 0) return false; buf_widx += rc; - codecbufused += rc; + filebufused += rc; fill_bytesleft -= rc; - if (buf_widx >= codecbuflen) - buf_widx -= codecbuflen; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; i += rc; } close(fd); @@ -840,20 +1022,23 @@ bool audio_load_track(int offset, bool start_play, int peek_offset) tracks[track_widx].playlist_offset = peek_offset; last_peek_offset = peek_offset; - if (buf_widx >= codecbuflen) - buf_widx -= codecbuflen; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; /* Set default values */ if (start_play) { + int last_codec = current_codec; + current_codec = CODEC_IDX_AUDIO; conf_bufferlimit = 0; conf_watermark = AUDIO_DEFAULT_WATERMARK; conf_filechunk = AUDIO_DEFAULT_FILECHUNK; dsp_configure(DSP_RESET, 0); ci.configure(CODEC_DSP_ENABLE, false); + current_codec = last_codec; } /* Load the codec. */ - tracks[track_widx].codecbuf = &codecbuf[buf_widx]; + tracks[track_widx].codecbuf = &filebuf[buf_widx]; if (!loadcodec(trackname, start_play)) { close(fd); /* Stop buffer filling if codec load failed. */ @@ -870,7 +1055,7 @@ bool audio_load_track(int offset, bool start_play, int peek_offset) } return false; } - // tracks[track_widx].filebuf = &codecbuf[buf_widx]; + // tracks[track_widx].filebuf = &filebuf[buf_widx]; tracks[track_widx].start_pos = 0; /* Get track metadata if we don't already have it. */ @@ -933,10 +1118,10 @@ bool audio_load_track(int offset, bool start_play, int peek_offset) if (fill_bytesleft == 0) break ; - copy_n = MIN(conf_filechunk, codecbuflen - buf_widx); + copy_n = MIN(conf_filechunk, filebuflen - buf_widx); copy_n = MIN(size - i, copy_n); copy_n = MIN((int)fill_bytesleft, copy_n); - rc = read(fd, &codecbuf[buf_widx], copy_n); + rc = read(fd, &filebuf[buf_widx], copy_n); if (rc < copy_n) { logf("File error!"); tracks[track_widx].filesize = 0; @@ -945,12 +1130,12 @@ bool audio_load_track(int offset, bool start_play, int peek_offset) return false; } buf_widx += rc; - if (buf_widx >= codecbuflen) - buf_widx -= codecbuflen; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; i += rc; tracks[track_widx].available += rc; tracks[track_widx].filerem -= rc; - codecbufused += rc; + filebufused += rc; fill_bytesleft -= rc; } @@ -997,10 +1182,10 @@ void audio_play_start(int offset) track_ridx = 0; buf_ridx = 0; buf_widx = 0; - codecbufused = 0; + filebufused = 0; pcmbuf_set_boost_mode(true); - fill_bytesleft = codecbuflen; + fill_bytesleft = filebuflen; filling = true; last_peek_offset = -1; if (audio_load_track(offset, true, 0)) { @@ -1089,7 +1274,7 @@ void initialize_buffer_fill(void) int cur_idx, i; - fill_bytesleft = codecbuflen - codecbufused; + fill_bytesleft = filebuflen - filebufused; cur_ti->start_pos = ci.curpos; pcmbuf_set_boost_mode(true); @@ -1124,7 +1309,7 @@ void initialize_buffer_fill(void) void audio_check_buffer(void) { /* Start buffer filling as necessary. */ - if ((codecbufused > conf_watermark || !queue_empty(&audio_queue) + if ((filebufused > conf_watermark || !queue_empty(&audio_queue) || !playing || ci.stop_codec || ci.reload_codec) && !filling) return ; @@ -1132,8 +1317,8 @@ void audio_check_buffer(void) /* Limit buffering size at first run. */ if (conf_bufferlimit && fill_bytesleft > conf_bufferlimit - - codecbufused) { - fill_bytesleft = MAX(0, conf_bufferlimit - codecbufused); + - filebufused) { + fill_bytesleft = MAX(0, conf_bufferlimit - filebufused); } /* Try to load remainings of the file. */ @@ -1169,27 +1354,27 @@ void audio_update_trackinfo(void) { if (new_track >= 0) { buf_ridx += cur_ti->available; - codecbufused -= cur_ti->available; + filebufused -= cur_ti->available; cur_ti = &tracks[track_ridx]; buf_ridx += cur_ti->codecsize; - codecbufused -= cur_ti->codecsize; - if (buf_ridx >= codecbuflen) - buf_ridx -= codecbuflen; + filebufused -= cur_ti->codecsize; + if (buf_ridx >= filebuflen) + buf_ridx -= filebuflen; if (!filling) pcmbuf_set_boost_mode(false); } else { buf_ridx -= ci.curpos + cur_ti->codecsize; - codecbufused += ci.curpos + cur_ti->codecsize; + filebufused += ci.curpos + cur_ti->codecsize; cur_ti->available = cur_ti->filesize; cur_ti = &tracks[track_ridx]; buf_ridx -= cur_ti->filesize; - codecbufused += cur_ti->filesize; + filebufused += cur_ti->filesize; cur_ti->available = cur_ti->filesize; if (buf_ridx < 0) - buf_ridx = codecbuflen + buf_ridx; + buf_ridx = filebuflen + buf_ridx; } ci.filesize = cur_ti->filesize; @@ -1220,7 +1405,7 @@ static void audio_stop_playback(void) current_fd = -1; } pcmbuf_play_stop(); - while (codec_loaded) + while (audio_codec_loaded) yield(); pcm_play_pause(true); track_count = 0; @@ -1267,6 +1452,11 @@ static int get_codec_base_type(int type) bool codec_request_next_track_callback(void) { + if (current_codec == CODEC_IDX_VOICE) { + voice_remaining = 0; + return !ci_voice.stop_codec; + } + if (ci.stop_codec || !playing) return false; @@ -1309,8 +1499,8 @@ bool codec_request_next_track_callback(void) if (--track_ridx < 0) track_ridx = MAX_TRACK-1; if (tracks[track_ridx].filesize == 0 || - codecbufused+ci.curpos+tracks[track_ridx].filesize - /*+ (off_t)tracks[track_ridx].codecsize*/ > codecbuflen) { + filebufused+ci.curpos+tracks[track_ridx].filesize + /*+ (off_t)tracks[track_ridx].codecsize*/ > filebuflen) { logf("Loading from disk..."); new_track = 0; last_index = -1; @@ -1379,10 +1569,10 @@ void audio_invalidate_tracks(void) track_widx = track_ridx; /* Mark all other entries null (also buffered wrong metadata). */ audio_clear_track_entries(false); - codecbufused = cur_ti->available; + filebufused = cur_ti->available; buf_widx = buf_ridx + cur_ti->available; - if (buf_widx >= codecbuflen) - buf_widx -= codecbuflen; + if (buf_widx >= filebuflen) + buf_widx -= filebuflen; read_next_metadata(); } @@ -1436,7 +1626,7 @@ void audio_thread(void) ci.reload_codec = false; ci.seek_time = 0; pcmbuf_crossfade_init(CROSSFADE_MODE_CROSSFADE); - while (codec_loaded) + while (audio_codec_loaded) yield(); audio_play_start((int)ev.data); playlist_update_resume_info(audio_current_track()); @@ -1462,11 +1652,13 @@ void audio_thread(void) case AUDIO_NEXT: logf("audio_next"); + pcmbuf_beep(5000, 100, 5000); initiate_track_change(1); break ; case AUDIO_PREV: logf("audio_prev"); + pcmbuf_beep(5000, 100, 5000); initiate_track_change(-1); break; @@ -1514,8 +1706,11 @@ void codec_thread(void) switch (ev.id) { case CODEC_LOAD_DISK: ci.stop_codec = false; - codec_loaded = true; - status = codec_load_file((char *)ev.data); + audio_codec_loaded = true; + mutex_lock(&mutex_codecthread); + current_codec = CODEC_IDX_AUDIO; + status = codec_load_file((char *)ev.data, &ci); + mutex_unlock(&mutex_codecthread); break ; case CODEC_LOAD: @@ -1531,10 +1726,13 @@ void codec_thread(void) } ci.stop_codec = false; - wrap = (int)&codecbuf[codecbuflen] - (int)cur_ti->codecbuf; - codec_loaded = true; - status = codec_load_ram(cur_ti->codecbuf, codecsize, - &codecbuf[0], wrap); + wrap = (int)&filebuf[filebuflen] - (int)cur_ti->codecbuf; + audio_codec_loaded = true; + mutex_lock(&mutex_codecthread); + current_codec = CODEC_IDX_AUDIO; + status = codec_load_ram(cur_ti->codecbuf, codecsize, + &filebuf[0], wrap, &ci); + mutex_unlock(&mutex_codecthread); break ; #ifndef SIMULATOR @@ -1545,7 +1743,7 @@ void codec_thread(void) #endif } - codec_loaded = false; + audio_codec_loaded = false; switch (ev.id) { case CODEC_LOAD_DISK: @@ -1569,6 +1767,83 @@ void codec_thread(void) } } +static void reset_buffer(void) +{ + filebuf = &audiobuf[MALLOC_BUFSIZE]; + filebuflen = audiobufend - audiobuf - pcmbuf_get_bufsize() + - PCMBUF_GUARD - MALLOC_BUFSIZE - GUARD_BUFSIZE; + + if (talk_get_bufsize() && voice_codec_loaded) + { + filebuf = &filebuf[talk_get_bufsize()]; + filebuflen -= 2*CODEC_IRAM_SIZE + 2*CODEC_SIZE + talk_get_bufsize(); + } +} + +void voice_codec_thread(void) +{ + struct event ev; + int status; + + current_codec = CODEC_IDX_AUDIO; + voice_codec_loaded = false; + while (1) { + status = 0; + queue_wait(&voice_codec_queue, &ev); + switch (ev.id) { + case CODEC_LOAD_DISK: + logf("Loading voice codec"); + audio_stop_playback(); + mutex_lock(&mutex_codecthread); + current_codec = CODEC_IDX_VOICE; + dsp_configure(DSP_RESET, 0); + ci.configure(CODEC_DSP_ENABLE, (bool *)true); + voice_remaining = 0; + voice_getmore = NULL; + voice_codec_loaded = true; + reset_buffer(); + ci_voice.stop_codec = false; + + status = codec_load_file((char *)ev.data, &ci_voice); + + logf("Voice codec finished"); + audio_stop_playback(); + mutex_unlock(&mutex_codecthread); + current_codec = CODEC_IDX_AUDIO; + voice_codec_loaded = false; + reset_buffer(); + break ; + +#ifndef SIMULATOR + case SYS_USB_CONNECTED: + usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_wait_for_disconnect(&voice_codec_queue); + break ; +#endif + } + } +} + +void voice_init(void) +{ + while (voice_codec_loaded) + { + logf("Terminating voice codec"); + ci_voice.stop_codec = true; + if (current_codec != CODEC_IDX_VOICE) + swap_codec(); + sleep(1); + } + + if (!talk_get_bufsize()) + return ; + + logf("Starting voice codec"); + queue_post(&voice_codec_queue, CODEC_LOAD_DISK, (void *)CODEC_MPA_L3); + while (!voice_codec_loaded) + sleep(1); +} + struct mp3entry* audio_current_track(void) { // logf("audio_current_track"); @@ -1620,7 +1895,7 @@ void audio_stop(void) { logf("audio_stop"); queue_post(&audio_queue, AUDIO_STOP, 0); - while (playing || codec_loaded) + while (playing || audio_codec_loaded) yield(); } @@ -1805,6 +2080,16 @@ int mp3_get_file_pos(void) return pos; } +void mp3_play_data(const unsigned char* start, int size, + void (*get_more)(unsigned char** start, int* size)) +{ + voice_getmore = get_more; + voicebuf = (unsigned char *)start; + voice_remaining = size; + voice_is_playing = true; + pcmbuf_reset_mixpos(); +} + void audio_set_buffer_margin(int setting) { int lookup[] = {5, 15, 30, 60, 120, 180, 300, 600}; @@ -1827,7 +2112,7 @@ void audio_set_crossfade(int type) offset = cur_ti->id3.offset; if (type == CROSSFADE_MODE_OFF) - seconds = 0; + seconds = 1; /* Buffer has to be at least 2s long. */ seconds += 2; @@ -1843,12 +2128,13 @@ void audio_set_crossfade(int type) if (was_playing) splash(0, true, str(LANG_RESTARTING_PLAYBACK)); pcmbuf_init(size); - pcmbuf_crossfade_enable(seconds > 2); - codecbuflen = audiobufend - audiobuf - pcmbuf_get_bufsize() - - PCMBUF_GUARD - MALLOC_BUFSIZE - GUARD_BUFSIZE; + pcmbuf_crossfade_enable(type != CROSSFADE_MODE_OFF); + reset_buffer(); logf("abuf:%dB", pcmbuf_get_bufsize()); - logf("fbuf:%dB", codecbuflen); + logf("fbuf:%dB", filebuflen); + voice_init(); + /* Restart playback. */ if (was_playing) { audio_play(offset); @@ -1856,7 +2142,7 @@ void audio_set_crossfade(int type) /* Wait for the playback to start again (and display the splash screen during that period. */ playing = true; - while (playing && !codec_loaded) + while (playing && !audio_codec_loaded) yield(); } } @@ -1884,13 +2170,17 @@ void test_unbuffer_event(struct mp3entry *id3, bool last_track) void audio_init(void) { + static bool voicetagtrue = true; + logf("audio api init"); pcm_init(); - codecbufused = 0; + filebufused = 0; filling = false; - codecbuf = &audiobuf[MALLOC_BUFSIZE]; + current_codec = CODEC_IDX_AUDIO; + filebuf = &audiobuf[MALLOC_BUFSIZE]; playing = false; - codec_loaded = false; + audio_codec_loaded = false; + voice_is_playing = false; paused = false; track_changed = false; current_fd = -1; @@ -1918,12 +2208,25 @@ void audio_init(void) ci.set_offset = codec_set_offset_callback; ci.configure = codec_configure_callback; + memcpy(&ci_voice, &ci, sizeof(struct codec_api)); + memset(&id3_voice, 0, sizeof(struct mp3entry)); + ci_voice.taginfo_ready = &voicetagtrue; + ci_voice.id3 = &id3_voice; + ci_voice.pcmbuf_insert = codec_pcmbuf_insert_callback; + id3_voice.frequency = 11200; + id3_voice.length = 1000000L; + mutex_init(&mutex_bufferfill); + mutex_init(&mutex_codecthread); + queue_init(&audio_queue); queue_init(&codec_queue); + queue_init(&voice_codec_queue); create_thread(codec_thread, codec_stack, sizeof(codec_stack), codec_thread_name); + create_thread(voice_codec_thread, voice_codec_stack, + sizeof(voice_codec_stack), voice_codec_thread_name); create_thread(audio_thread, audio_stack, sizeof(audio_stack), audio_thread_name); } diff --git a/apps/playback.h b/apps/playback.h index 7ed9a4b700..a5b64ba0e3 100644 --- a/apps/playback.h +++ b/apps/playback.h @@ -72,6 +72,7 @@ void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3, void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3, bool last_track)); void audio_invalidate_tracks(void); +void voice_init(void); #endif diff --git a/apps/playlist.c b/apps/playlist.c index 68fd8be369..bd443e4f38 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -1413,9 +1413,14 @@ int playlist_resume(void) }; /* use mp3 buffer for maximum load speed */ +#if CONFIG_HWCODEC != MASNONE talk_buffer_steal(); /* we use the mp3 buffer, need to tell */ buflen = (audiobufend - audiobuf); buffer = audiobuf; +#else + buflen = (audiobufend - audiobuf - talk_get_bufsize()); + buffer = &audiobuf[talk_get_bufsize()]; +#endif empty_playlist(playlist, true); @@ -1827,7 +1832,9 @@ int playlist_start(int start_index, int offset) struct playlist_info* playlist = ¤t_playlist; playlist->index = start_index; +#if CONFIG_HWCODEC != MASNONE talk_buffer_steal(); /* will use the mp3 buffer */ +#endif audio_play(offset); return 0; diff --git a/apps/talk.c b/apps/talk.c index a896ca3a1a..b417046a61 100644 --- a/apps/talk.c +++ b/apps/talk.c @@ -32,7 +32,11 @@ #include "lang.h" #include "talk.h" #include "id3.h" +#include "logf.h" #include "bitswap.h" +#if CONFIG_HWCODEC == MASNONE +#include "playback.h" +#endif /***************** Constants *****************/ @@ -88,6 +92,7 @@ static int filehandle; /* global, so the MMC variant can keep the file open */ static unsigned char* p_silence; /* VOICE_PAUSE clip, used for termination */ static long silence_len; /* length of the VOICE_PAUSE clip */ static unsigned char* p_lastclip; /* address of latest clip, for silence add */ +static unsigned long voicefile_size = 0; /* size of the loaded voice file */ /***************** Private prototypes *****************/ @@ -114,10 +119,28 @@ static int open_voicefile(void) } snprintf(buf, sizeof(buf), ROCKBOX_DIR LANG_DIR "/%s.voice", p_lang); - + return open(buf, O_RDONLY); } +int talk_get_bufsize(void) +{ + return voicefile_size; +} + +#ifdef SIMULATOR +static unsigned short BSWAP16(unsigned short value) +{ + return (value >> 8) | (value << 8); +} + +static unsigned long BSWAP32(unsigned long value) +{ + unsigned long hi = BSWAP16(value >> 16); + unsigned long lo = BSWAP16(value & 0xffff); + return (lo << 16) | hi; +} +#endif /* load the voice file into the mp3 buffer */ static void load_voicefile(void) @@ -125,6 +148,10 @@ static void load_voicefile(void) int load_size; int got_size; int file_size; +#if CONFIG_HWCODEC == MASNONE + int length, i; + unsigned char *buf, temp; +#endif filehandle = open_voicefile(); if (filehandle < 0) /* failed to open */ @@ -141,8 +168,20 @@ static void load_voicefile(void) #endif got_size = read(filehandle, audiobuf, load_size); - if (got_size == load_size /* success */ - && ((struct voicefile*)audiobuf)->table /* format check */ + if (got_size != load_size /* failure */) + goto load_err; + +#ifdef SIMULATOR + logf("Byte swapping voice file"); + p_voicefile = (struct voicefile*)audiobuf; + p_voicefile->version = BSWAP32(p_voicefile->version); + p_voicefile->table = BSWAP32(p_voicefile->table); + p_voicefile->id1_max = BSWAP32(p_voicefile->id1_max); + p_voicefile->id2_max = BSWAP32(p_voicefile->id2_max); + p_voicefile = NULL; +#endif + + if (((struct voicefile*)audiobuf)->table /* format check */ == offsetof(struct voicefile, index)) { p_voicefile = (struct voicefile*)audiobuf; @@ -155,7 +194,42 @@ static void load_voicefile(void) else goto load_err; -#ifdef HAVE_MMC +#ifdef SIMULATOR + for (i = 0; i < p_voicefile->id1_max + p_voicefile->id2_max; i++) + { + struct clip_entry *ce; + ce = &p_voicefile->index[i]; + ce->offset = BSWAP32(ce->offset); + ce->size = BSWAP32(ce->size); + } +#endif + + /* Do a bitswap as necessary. */ +#if CONFIG_HWCODEC == MASNONE + logf("Bitswapping voice file."); + cpu_boost(true); + buf = (unsigned char *)(&p_voicefile->index) + + (p_voicefile->id1_max + p_voicefile->id2_max) * sizeof(struct clip_entry); + length = file_size - offsetof(struct voicefile, index) - + (p_voicefile->id1_max - p_voicefile->id2_max) * sizeof(struct clip_entry); + + for (i = 0; i < length; i++) + { + temp = buf[i]; + buf[i] = ((temp >> 7) & 0x01) + | ((temp >> 5) & 0x02) + | ((temp >> 3) & 0x04) + | ((temp >> 1) & 0x08) + | ((temp << 1) & 0x10) + | ((temp << 3) & 0x20) + | ((temp << 5) & 0x40) + | ((temp << 7) & 0x80); + } + cpu_boost(false); + +#endif + +#ifdef HAVE_MMC /* load the index table, now that we know its size from the header */ load_size = (p_voicefile->id1_max + p_voicefile->id2_max) * sizeof(struct clip_entry); @@ -193,7 +267,11 @@ static void mp3_callback(unsigned char** start, int* size) if (queue[queue_read].len > 0) /* current clip not finished? */ { /* feed the next 64K-1 chunk */ +#if CONFIG_HWCODEC != MASNONE sent = MIN(queue[queue_read].len, 0xFFFF); +#else + sent = queue[queue_read].len; +#endif *start = queue[queue_read].buf; *size = sent; return; @@ -207,7 +285,11 @@ re_check: if (QUEUE_LEVEL) /* queue is not empty? */ { /* start next clip */ +#if CONFIG_HWCODEC != MASNONE sent = MIN(queue[queue_read].len, 0xFFFF); +#else + sent = queue[queue_read].len; +#endif *start = p_lastclip = queue[queue_read].buf; *size = sent; curr_hd[0] = p_lastclip[1]; @@ -286,7 +368,7 @@ static int shutup(void) /* nothing to do, was frame boundary or not our clip */ mp3_play_stop(); queue_write = queue_read = 0; /* reset the queue */ - + return 0; } @@ -317,7 +399,11 @@ static int queue_clip(unsigned char* buf, long size, bool enqueue) if (queue_level == 0) { /* queue was empty, we have to do the initial start */ p_lastclip = buf; +#if CONFIG_HWCODEC != MASNONE sent = MIN(size, 0xFFFF); /* DMA can do no more */ +#else + sent = size; +#endif mp3_play_data(buf, sent, mp3_callback); curr_hd[0] = buf[1]; curr_hd[1] = buf[2]; @@ -400,12 +486,19 @@ void talk_init(void) #else filehandle = open_voicefile(); has_voicefile = (filehandle >= 0); /* test if we can open it */ + voicefile_size = 0; + if (has_voicefile) { + voicefile_size = filesize(filehandle); +#if CONFIG_HWCODEC == MASNONE + voice_init(); +#endif close(filehandle); /* close again, this was just to detect presence */ filehandle = -1; } #endif + } @@ -432,8 +525,10 @@ int talk_id(long id, bool enqueue) unsigned char* clipbuf; int unit; +#if CONFIG_HWCODEC != MASNONE if (audio_status()) /* busy, buffer in use */ return -1; +#endif if (p_voicefile == NULL && has_voicefile) load_voicefile(); /* reload needed */ @@ -514,8 +609,10 @@ int talk_number(long n, bool enqueue) int level = 0; /* mille count */ long mil = 1000000000; /* highest possible "-illion" */ +#if CONFIG_HWCODEC != MASNONE if (audio_status()) /* busy, buffer in use */ return -1; +#endif if (!enqueue) shutup(); /* cut off all the pending stuff */ @@ -593,8 +690,10 @@ int talk_value(long n, int unit, bool enqueue) VOICE_HERTZ, }; +#if CONFIG_HWCODEC != MASNONE if (audio_status()) /* busy, buffer in use */ return -1; +#endif if (unit < 0 || unit >= UNIT_LAST) unit_id = -1; @@ -625,8 +724,10 @@ int talk_spell(const char* spell, bool enqueue) { char c; /* currently processed char */ +#if CONFIG_HWCODEC != MASNONE if (audio_status()) /* busy, buffer in use */ return -1; +#endif if (!enqueue) shutup(); /* cut off all the pending stuff */ diff --git a/apps/talk.h b/apps/talk.h index 213e1803d4..18314e52c5 100644 --- a/apps/talk.h +++ b/apps/talk.h @@ -59,6 +59,7 @@ extern const char* const dir_thumbnail_name; /* "_dirname.talk" */ extern const char* const file_thumbnail_ext; /* ".talk" for file voicing */ void talk_init(void); +int talk_get_bufsize(void); /* get the loaded voice file size */ int talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */ int talk_id(long id, bool enqueue); /* play a voice ID from voicefont */ int talk_file(const char* filename, bool enqueue); /* play a thumbnail from file */ diff --git a/firmware/mp3_playback.c b/firmware/mp3_playback.c index dfe08e5d3a..3a2fdb4f1a 100644 --- a/firmware/mp3_playback.c +++ b/firmware/mp3_playback.c @@ -576,7 +576,6 @@ void mp3_reset_playtime(void) playstart_tick = current_tick; } - bool mp3_is_playing(void) { return playing; @@ -624,18 +623,11 @@ void mp3_init(int volume, int bass, int treble, int balance, int loudness, audio_is_initialized = true; #endif } + void mp3_shutdown(void) { /* a dummy */ } -void mp3_play_data(const unsigned char* start, int size, - void (*get_more)(unsigned char** start, int* size)) -{ - /* a dummy */ - (void)start; - (void)size; - (void)get_more; -} void mp3_play_stop(void) { @@ -653,4 +645,10 @@ unsigned char* mp3_get_pos(void) /* a dummy */ return (unsigned char *)0x1234; } + +bool mp3_is_playing(void) +{ + return playing; +} + #endif /* CONFIG_HWCODEC == MASNONE */ diff --git a/firmware/sound.c b/firmware/sound.c index 8fb015c27a..cd772f5e9f 100644 --- a/firmware/sound.c +++ b/firmware/sound.c @@ -704,10 +704,3 @@ int sound_get_pitch(void) } #endif -#if CONFIG_HWCODEC == MASNONE -bool mp3_is_playing(void) -{ - /* a dummy */ - return false; -} -#endif diff --git a/uisimulator/common/stubs.c b/uisimulator/common/stubs.c index af6f9653fe..2357f9baea 100644 --- a/uisimulator/common/stubs.c +++ b/uisimulator/common/stubs.c @@ -273,6 +273,7 @@ void button_set_flip(bool yesno) (void)yesno; } +#if CONFIG_HWCODEC != MASNONE void talk_init(void) { } @@ -320,6 +321,7 @@ int talk_spell(char* spell, bool enqueue) const char* const dir_thumbnail_name = "_dirname.talk"; const char* const file_thumbnail_ext = ".talk"; +#endif /* FIXME: this shoudn't be a stub, rather the real thing. I'm afraid on Win32/X11 it'll be hard to kill a thread from outside. */