diff --git a/apps/SOURCES b/apps/SOURCES index 5e097049c2..597eb48b23 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -15,6 +15,7 @@ menus/theme_menu.c #if CONFIG_CODEC == SWCODEC menus/eq_menu.c buffering.c +voice_thread.c #endif menus/main_menu.c menus/playback_menu.c diff --git a/apps/codecs.c b/apps/codecs.c index 88ecd24ce4..f2539dc73e 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -67,8 +67,6 @@ extern unsigned char codecbuf[]; extern void* plugin_get_audio_buffer(size_t *buffer_size); -struct codec_api ci_voice; - struct codec_api ci = { 0, /* filesize */ @@ -163,6 +161,8 @@ struct codec_api ci = { flush_icache, invalidate_icache, #endif + + NULL, /* struct sp_data *dsp */ }; void codec_get_full_path(char *path, const char *codec_root_fn) diff --git a/apps/codecs.h b/apps/codecs.h index ecba1e99ef..d2ba00ca2a 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -80,7 +80,7 @@ #define CODEC_ENC_MAGIC 0x52454E43 /* RENC */ /* increase this every time the api struct changes */ -#define CODEC_API_VERSION 19 +#define CODEC_API_VERSION 20 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -234,6 +234,8 @@ struct codec_api { void (*flush_icache)(void); void (*invalidate_icache)(void); #endif + + struct dsp_config *dsp; }; /* codec header */ diff --git a/apps/codecs/libspeex/bits.c b/apps/codecs/libspeex/bits.c index e460a39cf2..4629012c57 100644 --- a/apps/codecs/libspeex/bits.c +++ b/apps/codecs/libspeex/bits.c @@ -45,6 +45,7 @@ #define MAX_CHARS_PER_FRAME (2000/BYTES_PER_CHAR) #endif +#ifndef ROCKBOX_VOICE_CODEC void speex_bits_init(SpeexBits *bits) { bits->chars = (char*)speex_alloc(MAX_CHARS_PER_FRAME); @@ -57,6 +58,7 @@ void speex_bits_init(SpeexBits *bits) speex_bits_reset(bits); } +#endif void speex_bits_init_buffer(SpeexBits *bits, void *buff, int buf_size) { @@ -82,12 +84,14 @@ void speex_bits_set_bit_buffer(SpeexBits *bits, void *buff, int buf_size) } +#ifndef ROCKBOX_VOICE_CODEC void speex_bits_destroy(SpeexBits *bits) { if (bits->owner) speex_free(bits->chars); /* Will do something once the allocation is dynamic */ } +#endif void speex_bits_reset(SpeexBits *bits) { @@ -106,7 +110,7 @@ void speex_bits_rewind(SpeexBits *bits) bits->overflow=0; } -#ifndef SPEEX_VOICE_ENCODER +#if !defined(SPEEX_VOICE_ENCODER) && !defined(ROCKBOX_VOICE_CODEC) void speex_bits_read_from(SpeexBits *bits, char *chars, int len) { int i; diff --git a/apps/codecs/libspeex/config-speex.h b/apps/codecs/libspeex/config-speex.h index ad1393fc60..70d86f6299 100644 --- a/apps/codecs/libspeex/config-speex.h +++ b/apps/codecs/libspeex/config-speex.h @@ -45,6 +45,12 @@ #define FLOATING_POINT #endif +#ifndef ROCKBOX_VOICE_CODEC +#define EXC_ICONST_ATTR ICONST_ATTR +#else +#define EXC_ICONST_ATTR +#endif + /* Define to 1 if you have the header file. */ /* #undef HAVE_DLFCN_H */ diff --git a/apps/codecs/libspeex/exc_10_16_table.c b/apps/codecs/libspeex/exc_10_16_table.c index 2184e9c955..755c5a0b7f 100644 --- a/apps/codecs/libspeex/exc_10_16_table.c +++ b/apps/codecs/libspeex/exc_10_16_table.c @@ -32,7 +32,7 @@ #include "config-speex.h" -const signed char exc_10_16_table[160] ICONST_ATTR = { +const signed char exc_10_16_table[160] EXC_ICONST_ATTR = { 22,39,14,44,11,35,-2,23,-4,6, 46,-28,13,-27,-23,12,4,20,-5,9, 37,-18,-23,23,0,9,-6,-20,4,-1, diff --git a/apps/codecs/libspeex/exc_10_32_table.c b/apps/codecs/libspeex/exc_10_32_table.c index ac8cda03c8..1c94b5511c 100644 --- a/apps/codecs/libspeex/exc_10_32_table.c +++ b/apps/codecs/libspeex/exc_10_32_table.c @@ -32,7 +32,7 @@ #include "config-speex.h" -const signed char exc_10_32_table[320] ICONST_ATTR = { +const signed char exc_10_32_table[320] EXC_ICONST_ATTR = { 7,17,17,27,25,22,12,4,-3,0, 28,-36,39,-24,-15,3,-9,15,-5,10, 31,-28,11,31,-21,9,-11,-11,-2,-7, diff --git a/apps/codecs/libspeex/exc_20_32_table.c b/apps/codecs/libspeex/exc_20_32_table.c index fff3bed944..40dbb34e9e 100644 --- a/apps/codecs/libspeex/exc_20_32_table.c +++ b/apps/codecs/libspeex/exc_20_32_table.c @@ -32,7 +32,7 @@ #include "config-speex.h" -const signed char exc_20_32_table[640] ICONST_ATTR = { +const signed char exc_20_32_table[640] EXC_ICONST_ATTR = { 12,32,25,46,36,33,9,14,-3,6,1,-8,0,-10,-5,-7,-7,-7,-5,-5, 31,-27,24,-32,-4,10,-11,21,-3,19,23,-9,22,24,-10,-1,-10,-13,-7,-11, 42,-33,31,19,-8,0,-10,-16,1,-21,-17,10,-8,14,8,4,11,-2,5,-2, diff --git a/apps/codecs/libspeex/exc_5_256_table.c b/apps/codecs/libspeex/exc_5_256_table.c index 6af987fea4..1a32057956 100644 --- a/apps/codecs/libspeex/exc_5_256_table.c +++ b/apps/codecs/libspeex/exc_5_256_table.c @@ -32,7 +32,7 @@ #include "config-speex.h" -const signed char exc_5_256_table[1280] ICONST_ATTR = { +const signed char exc_5_256_table[1280] EXC_ICONST_ATTR = { -8,-37,5,-43,5, 73,61,39,12,-3, -61,-32,2,42,30, diff --git a/apps/codecs/libspeex/exc_5_64_table.c b/apps/codecs/libspeex/exc_5_64_table.c index cd03eb5a20..7d29f60373 100644 --- a/apps/codecs/libspeex/exc_5_64_table.c +++ b/apps/codecs/libspeex/exc_5_64_table.c @@ -32,7 +32,7 @@ #include "config-speex.h" -const signed char exc_5_64_table[320] ICONST_ATTR = { +const signed char exc_5_64_table[320] EXC_ICONST_ATTR = { 1,5,-15,49,-66, -48,-4,50,-44,7, 37,16,-18,25,-26, diff --git a/apps/codecs/libspeex/exc_8_128_table.c b/apps/codecs/libspeex/exc_8_128_table.c index 3a910bee37..02a58e052c 100644 --- a/apps/codecs/libspeex/exc_8_128_table.c +++ b/apps/codecs/libspeex/exc_8_128_table.c @@ -32,7 +32,7 @@ #include "config-speex.h" -const signed char exc_8_128_table[1024] ICONST_ATTR = { +const signed char exc_8_128_table[1024] EXC_ICONST_ATTR = { -14,9,13,-32,2,-10,31,-10, -8,-8,6,-4,-1,10,-64,23, 6,20,13,6,8,-22,16,34, diff --git a/apps/dsp.c b/apps/dsp.c index 4cade936b9..6b2c698532 100644 --- a/apps/dsp.c +++ b/apps/dsp.c @@ -162,6 +162,10 @@ struct dsp_config int sample_bytes; int stereo_mode; int frac_bits; +#ifdef HAVE_SW_TONE_CONTROLS + /* Filter struct for software bass/treble controls */ + struct eqfilter tone_filter; +#endif /* Functions that change depending upon settings - NULL if stage is disabled */ sample_input_fn_type input_samples; @@ -171,6 +175,7 @@ struct dsp_config way */ channels_process_dsp_fn_type apply_gain; channels_process_fn_type apply_crossfeed; + channels_process_fn_type eq_process; channels_process_fn_type channels_process; }; @@ -187,13 +192,13 @@ struct crossfeed_data crossfeed_data IDATA_ATTR = /* A */ }; /* Equalizer */ -static struct eq_state eq_data; /* A/V */ +static struct eq_state eq_data; /* A */ + +/* Software tone controls */ #ifdef HAVE_SW_TONE_CONTROLS -static int prescale; -static int bass; -static int treble; -/* Filter struct for software bass/treble controls */ -static struct eqfilter tone_filter; +static int prescale; /* A/V */ +static int bass; /* A/V */ +static int treble; /* A/V */ #endif /* Settings applicable to audio codec only */ @@ -202,7 +207,6 @@ static int channels_mode; long dsp_sw_gain; long dsp_sw_cross; static bool dither_enabled; -static bool eq_enabled IBSS_ATTR; static long eq_precut; static long track_gain; static bool new_gain; @@ -212,9 +216,8 @@ static long album_peak; static long replaygain; static bool crossfeed_enabled; -#define audio_dsp (&dsp_conf[CODEC_IDX_AUDIO]) -#define voice_dsp (&dsp_conf[CODEC_IDX_VOICE]) -static struct dsp_config *dsp IDATA_ATTR = audio_dsp; +#define audio_dsp (dsp_conf[CODEC_IDX_AUDIO]) +#define voice_dsp (dsp_conf[CODEC_IDX_VOICE]) /* The internal format is 32-bit samples, non-interleaved, stereo. This * format is similar to the raw output from several codecs, so the amount @@ -224,14 +227,6 @@ static struct dsp_config *dsp IDATA_ATTR = audio_dsp; int32_t sample_buf[SAMPLE_BUF_COUNT] IBSS_ATTR; static int32_t resample_buf[RESAMPLE_BUF_COUNT] IBSS_ATTR; -/* set a new dsp and return old one */ -static inline struct dsp_config * switch_dsp(struct dsp_config *_dsp) -{ - struct dsp_config * old_dsp = dsp; - dsp = _dsp; - return old_dsp; -} - #if 0 /* Clip sample to arbitrary limits where range > 0 and min + range = max */ static inline long clip_sample(int32_t sample, int32_t min, int32_t range) @@ -263,8 +258,8 @@ int sound_get_pitch(void) void sound_set_pitch(int permille) { pitch_ratio = permille; - - dsp_configure(DSP_SWITCH_FREQUENCY, dsp->codec_frequency); + dsp_configure(&audio_dsp, DSP_SWITCH_FREQUENCY, + audio_dsp.codec_frequency); } /* Convert count samples to the internal format, if needed. Updates src @@ -386,7 +381,7 @@ static void sample_input_gt_native_ni_stereo( * * dsp->stereo_mode (A/V) * * dsp->sample_depth (A/V) */ -static void sample_input_new_format(void) +static void sample_input_new_format(struct dsp_config *dsp) { static const sample_input_fn_type sample_input_functions[] = { @@ -462,7 +457,7 @@ static void sample_output_dithered(int count, struct dsp_data *data, int ch; int16_t *d; - for (ch = 0; ch < dsp->data.num_channels; ch++) + for (ch = 0; ch < data->num_channels; ch++) { struct dither_data * const dither = &dither_data[ch]; int32_t *s = src[ch]; @@ -505,7 +500,7 @@ static void sample_output_dithered(int count, struct dsp_data *data, } } - if (dsp->data.num_channels == 2) + if (data->num_channels == 2) return; /* Have to duplicate left samples into the right channel since @@ -530,7 +525,7 @@ static void sample_output_dithered(int count, struct dsp_data *data, * * dsp->stereo_mode (A/V) * * dither_enabled (A) */ -static void sample_output_new_format(void) +static void sample_output_new_format(struct dsp_config *dsp) { static const sample_output_fn_type sample_output_functions[] = { @@ -542,7 +537,7 @@ static void sample_output_new_format(void) int out = dsp->data.num_channels - 1; - if (dsp == audio_dsp && dither_enabled) + if (dsp == &audio_dsp && dither_enabled) out += 2; dsp->output_samples = sample_output_functions[out]; @@ -638,7 +633,7 @@ static int dsp_upsample(int count, struct dsp_data *data, } #endif /* DSP_HAVE_ASM_RESAMPLING */ -static void resampler_new_delta(void) +static void resampler_new_delta(struct dsp_config *dsp) { dsp->data.resample_data.delta = (unsigned long) dsp->frequency * 65536LL / NATIVE_FREQUENCY; @@ -663,7 +658,7 @@ static void resampler_new_delta(void) * done, to refer to the resampled data. Returns number of stereo samples * for further processing. */ -static inline int resample(int count, int32_t *src[]) +static inline int resample(struct dsp_config *dsp, int count, int32_t *src[]) { int32_t *dst[2] = { @@ -679,12 +674,8 @@ static inline int resample(int count, int32_t *src[]) return count; } -static void dither_init(void) +static void dither_init(struct dsp_config *dsp) { - /* Voice codec should not reset the audio codec's dither data */ - if (dsp != audio_dsp) - return; - memset(dither_data, 0, sizeof (dither_data)); dither_bias = (1L << (dsp->frac_bits - NATIVE_DEPTH)); dither_mask = (1L << (dsp->frac_bits + 1 - NATIVE_DEPTH)) - 1; @@ -692,11 +683,9 @@ static void dither_init(void) void dsp_dither_enable(bool enable) { - /* Be sure audio dsp is current to set correct function */ - struct dsp_config *old_dsp = switch_dsp(audio_dsp); + struct dsp_config *dsp = &audio_dsp; dither_enabled = enable; - sample_output_new_format(); - switch_dsp(old_dsp); + sample_output_new_format(dsp); } /* Applies crossfeed to the stereo signal in src. @@ -762,9 +751,8 @@ static void apply_crossfeed(int count, int32_t *buf[]) void dsp_set_crossfeed(bool enable) { crossfeed_enabled = enable; - audio_dsp->apply_crossfeed = - (enable && audio_dsp->data.num_channels > 1) - ? apply_crossfeed : NULL; + audio_dsp.apply_crossfeed = (enable && audio_dsp.data.num_channels > 1) + ? apply_crossfeed : NULL; } void dsp_set_crossfeed_direct_gain(int gain) @@ -830,12 +818,12 @@ static void set_gain(struct dsp_config *dsp) dsp->data.gain = DEFAULT_GAIN; /* Replay gain not relevant to voice */ - if (dsp == audio_dsp && replaygain) + if (dsp == &audio_dsp && replaygain) { dsp->data.gain = replaygain; } - if (eq_enabled && eq_precut) + if (dsp->eq_process && eq_precut) { dsp->data.gain = (long) (((int64_t) dsp->data.gain * eq_precut) >> 24); @@ -853,16 +841,6 @@ static void set_gain(struct dsp_config *dsp) dsp->apply_gain = dsp->data.gain != 0 ? dsp_apply_gain : NULL; } -/** - * Use to enable the equalizer. - * - * @param enable true to enable the equalizer - */ -void dsp_set_eq(bool enable) -{ - eq_enabled = enable; -} - /** * Update the amount to cut the audio before applying the equalizer. * @@ -871,8 +849,7 @@ void dsp_set_eq(bool enable) void dsp_set_eq_precut(int precut) { eq_precut = get_replaygain_int(precut * -10); - set_gain(audio_dsp); - set_gain(voice_dsp); /* For EQ precut */ + set_gain(&audio_dsp); } /** @@ -929,7 +906,7 @@ static void eq_process(int count, int32_t *buf[]) EQ_PEAK_SHIFT, /* peaking */ EQ_SHELF_SHIFT, /* high shelf */ }; - unsigned int channels = dsp->data.num_channels; + unsigned int channels = audio_dsp.data.num_channels; int i; /* filter configuration currently is 1 low shelf filter, 3 band peaking @@ -944,6 +921,17 @@ static void eq_process(int count, int32_t *buf[]) } } +/** + * Use to enable the equalizer. + * + * @param enable true to enable the equalizer + */ +void dsp_set_eq(bool enable) +{ + audio_dsp.eq_process = enable ? eq_process : NULL; + set_gain(&audio_dsp); +} + void dsp_set_stereo_width(int value) { long width, straight, cross; @@ -966,50 +954,6 @@ void dsp_set_stereo_width(int value) dsp_sw_cross = cross << 8; } -#if CONFIG_CODEC == SWCODEC - -#ifdef HAVE_SW_TONE_CONTROLS -static void set_tone_controls(void) -{ - filter_bishelf_coefs(0xffffffff/NATIVE_FREQUENCY*200, - 0xffffffff/NATIVE_FREQUENCY*3500, - bass, treble, -prescale, tone_filter.coefs); -} -#endif - -/* Hook back from firmware/ part of audio, which can't/shouldn't call apps/ - * code directly. - */ -int dsp_callback(int msg, intptr_t param) -{ - switch (msg) { -#ifdef HAVE_SW_TONE_CONTROLS - case DSP_CALLBACK_SET_PRESCALE: - prescale = param; - set_tone_controls(); - break; - /* prescaler is always set after calling any of these, so we wait with - * calculating coefs until the above case is hit. - */ - case DSP_CALLBACK_SET_BASS: - bass = param; - break; - case DSP_CALLBACK_SET_TREBLE: - treble = param; -#endif - case DSP_CALLBACK_SET_CHANNEL_CONFIG: - dsp_set_channel_config(param); - break; - case DSP_CALLBACK_SET_STEREO_WIDTH: - dsp_set_stereo_width(param); - break; - default: - break; - } - return 0; -} -#endif - /** * Implements the different channel configurations and stereo width. */ @@ -1098,14 +1042,64 @@ void dsp_set_channel_config(int value) }; if ((unsigned)value >= ARRAYLEN(channels_process_functions) || - audio_dsp->stereo_mode == STEREO_MONO) + audio_dsp.stereo_mode == STEREO_MONO) + { value = SOUND_CHAN_STEREO; + } /* This doesn't apply to voice */ channels_mode = value; - audio_dsp->channels_process = channels_process_functions[value]; + audio_dsp.channels_process = channels_process_functions[value]; } +#if CONFIG_CODEC == SWCODEC + +#ifdef HAVE_SW_TONE_CONTROLS +static void set_tone_controls(void) +{ + filter_bishelf_coefs(0xffffffff/NATIVE_FREQUENCY*200, + 0xffffffff/NATIVE_FREQUENCY*3500, + bass, treble, -prescale, + audio_dsp.tone_filter.coefs); + /* Sync the voice dsp coefficients */ + memcpy(&voice_dsp.tone_filter.coefs, audio_dsp.tone_filter.coefs, + sizeof (voice_dsp.tone_filter.coefs)); +} +#endif + +/* Hook back from firmware/ part of audio, which can't/shouldn't call apps/ + * code directly. + */ +int dsp_callback(int msg, intptr_t param) +{ + switch (msg) { +#ifdef HAVE_SW_TONE_CONTROLS + case DSP_CALLBACK_SET_PRESCALE: + prescale = param; + set_tone_controls(); + break; + /* prescaler is always set after calling any of these, so we wait with + * calculating coefs until the above case is hit. + */ + case DSP_CALLBACK_SET_BASS: + bass = param; + break; + case DSP_CALLBACK_SET_TREBLE: + treble = param; +#endif + case DSP_CALLBACK_SET_CHANNEL_CONFIG: + dsp_set_channel_config(param); + break; + case DSP_CALLBACK_SET_STEREO_WIDTH: + dsp_set_stereo_width(param); + break; + default: + break; + } + return 0; +} +#endif + /* Process and convert src audio to dst based on the DSP configuration, * reading count number of audio samples. dst is assumed to be large * enough; use dsp_output_count() to get the required number. src is an @@ -1114,7 +1108,7 @@ void dsp_set_channel_config(int value) * non-interleaved stereo, it contains two pointers, one for each audio * channel. Returns number of bytes written to dst. */ -int dsp_process(char *dst, const char *src[], int count) +int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count) { int32_t *tmp[2]; int written = 0; @@ -1142,25 +1136,19 @@ int dsp_process(char *dst, const char *src[], int count) if (dsp->apply_gain) dsp->apply_gain(samples, &dsp->data, tmp); - if (dsp->resample && (samples = resample(samples, tmp)) <= 0) + if (dsp->resample && (samples = resample(dsp, samples, tmp)) <= 0) break; /* I'm pretty sure we're downsampling here */ if (dsp->apply_crossfeed) dsp->apply_crossfeed(samples, tmp); - /* TODO: EQ and tone controls need separate structs for audio and voice - * DSP processing thanks to filter history. isn't really audible now, but - * might be the day we start handling voice more delicately. Planned - * changes may well run all relevent channels through the same EQ so - * perhaps not. - */ - if (eq_enabled) - eq_process(samples, tmp); + if (dsp->eq_process) + dsp->eq_process(samples, tmp); #ifdef HAVE_SW_TONE_CONTROLS if ((bass | treble) != 0) - eq_filter(tmp, &tone_filter, samples, dsp->data.num_channels, - FILTER_BISHELF_SHIFT); + eq_filter(tmp, &dsp->tone_filter, samples, + dsp->data.num_channels, FILTER_BISHELF_SHIFT); #endif if (dsp->channels_process) @@ -1187,7 +1175,7 @@ int dsp_process(char *dst, const char *src[], int count) * of the resampler). */ /* dsp_input_size MUST be called afterwards */ -int dsp_output_count(int count) +int dsp_output_count(struct dsp_config *dsp, int count) { if (dsp->resample) { @@ -1209,7 +1197,7 @@ int dsp_output_count(int count) /* Given count output samples, calculate number of input samples * that would be consumed in order to fill the output buffer. */ -int dsp_input_count(int count) +int dsp_input_count(struct dsp_config *dsp, int count) { /* count is now the number of resampled input samples. Convert to original input samples. */ @@ -1225,41 +1213,37 @@ int dsp_input_count(int count) return count; } -int dsp_stereo_mode(void) -{ - return dsp->stereo_mode; -} - static void dsp_set_gain_var(long *var, long value) { - /* Voice shouldn't mess with these */ - if (dsp == audio_dsp) - { - *var = value; - new_gain = true; - } + *var = value; + new_gain = true; } -static void dsp_update_functions(void) +static void dsp_update_functions(struct dsp_config *dsp) { - sample_input_new_format(); - sample_output_new_format(); - if (dsp == audio_dsp) + sample_input_new_format(dsp); + sample_output_new_format(dsp); + if (dsp == &audio_dsp) dsp_set_crossfeed(crossfeed_enabled); } -bool dsp_configure(int setting, intptr_t value) +intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value) { switch (setting) { - case DSP_SWITCH_CODEC: - if ((uintptr_t)value <= 1) - switch_dsp(&dsp_conf[value]); - break; + case DSP_MYDSP: + switch (value) + { + case CODEC_IDX_AUDIO: + return (intptr_t)&audio_dsp; + case CODEC_IDX_VOICE: + return (intptr_t)&voice_dsp; + default: + return (intptr_t)NULL; + } case DSP_SET_FREQUENCY: - memset(&dsp->data.resample_data, 0, - sizeof (dsp->data.resample_data)); + memset(&dsp->data.resample_data, 0, sizeof (dsp->data.resample_data)); /* Fall through!!! */ case DSP_SWITCH_FREQUENCY: dsp->codec_frequency = (value == 0) ? NATIVE_FREQUENCY : value; @@ -1267,12 +1251,12 @@ bool dsp_configure(int setting, intptr_t value) if we're called from the main audio thread. Voice UI thread should not need this feature. */ - if (dsp == audio_dsp) + if (dsp == &audio_dsp) dsp->frequency = pitch_ratio * dsp->codec_frequency / 1000; else dsp->frequency = dsp->codec_frequency; - resampler_new_delta(); + resampler_new_delta(dsp); break; case DSP_SET_SAMPLE_DEPTH: @@ -1294,14 +1278,14 @@ bool dsp_configure(int setting, intptr_t value) } dsp->data.output_scale = dsp->frac_bits + 1 - NATIVE_DEPTH; - sample_input_new_format(); - dither_init(); + sample_input_new_format(dsp); + dither_init(dsp); break; case DSP_SET_STEREO_MODE: dsp->stereo_mode = value; dsp->data.num_channels = value == STEREO_MONO ? 1 : 2; - dsp_update_functions(); + dsp_update_functions(dsp); break; case DSP_RESET: @@ -1315,7 +1299,7 @@ bool dsp_configure(int setting, intptr_t value) dsp->data.clip_min = -((1 << WORD_FRACBITS)); dsp->codec_frequency = dsp->frequency = NATIVE_FREQUENCY; - if (dsp == audio_dsp) + if (dsp == &audio_dsp) { track_gain = 0; album_gain = 0; @@ -1324,31 +1308,35 @@ bool dsp_configure(int setting, intptr_t value) new_gain = true; } - dsp_update_functions(); - resampler_new_delta(); + dsp_update_functions(dsp); + resampler_new_delta(dsp); break; case DSP_FLUSH: memset(&dsp->data.resample_data, 0, sizeof (dsp->data.resample_data)); - resampler_new_delta(); - dither_init(); + resampler_new_delta(dsp); + dither_init(dsp); break; case DSP_SET_TRACK_GAIN: - dsp_set_gain_var(&track_gain, value); + if (dsp == &audio_dsp) + dsp_set_gain_var(&track_gain, value); break; case DSP_SET_ALBUM_GAIN: - dsp_set_gain_var(&album_gain, value); + if (dsp == &audio_dsp) + dsp_set_gain_var(&album_gain, value); break; case DSP_SET_TRACK_PEAK: - dsp_set_gain_var(&track_peak, value); + if (dsp == &audio_dsp) + dsp_set_gain_var(&track_peak, value); break; case DSP_SET_ALBUM_PEAK: - dsp_set_gain_var(&album_peak, value); + if (dsp == &audio_dsp) + dsp_set_gain_var(&album_peak, value); break; default: @@ -1404,5 +1392,5 @@ void dsp_set_replaygain(void) /* Store in S8.23 format to simplify calculations. */ replaygain = gain; - set_gain(audio_dsp); + set_gain(&audio_dsp); } diff --git a/apps/dsp.h b/apps/dsp.h index 838dc617ee..799d023aee 100644 --- a/apps/dsp.h +++ b/apps/dsp.h @@ -32,10 +32,16 @@ enum STEREO_NUM_MODES, }; +enum +{ + CODEC_IDX_AUDIO = 0, + CODEC_IDX_VOICE, +}; + enum { CODEC_SET_FILEBUF_WATERMARK = 1, - DSP_SWITCH_CODEC, + DSP_MYDSP, DSP_SET_FREQUENCY, DSP_SWITCH_FREQUENCY, DSP_SET_SAMPLE_DEPTH, @@ -201,23 +207,25 @@ enum { #define DIV64(x, y, z) (long)(((long long)(x) << (z))/(y)) -int dsp_process(char *dest, const char *src[], int count); -int dsp_input_count(int count); -int dsp_output_count(int count); -int dsp_stereo_mode(void); -bool dsp_configure(int setting, intptr_t value); +struct dsp_config; + +int dsp_process(struct dsp_config *dsp, char *dest, + const char *src[], int count); +int dsp_input_count(struct dsp_config *dsp, int count); +int dsp_output_count(struct dsp_config *dsp, int count); +intptr_t dsp_configure(struct dsp_config *dsp, int setting, + intptr_t value); void dsp_set_replaygain(void); void dsp_set_crossfeed(bool enable); void dsp_set_crossfeed_direct_gain(int gain); -void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff); +void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, + long cutoff); void dsp_set_eq(bool enable); void dsp_set_eq_precut(int precut); void dsp_set_eq_coefs(int band); void sound_set_pitch(int r); int sound_get_pitch(void); int dsp_callback(int msg, intptr_t param); -void dsp_set_channel_config(int value); -void dsp_set_stereo_width(int value); void dsp_dither_enable(bool enable); #endif diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c index 49333d108b..4ed2973dbb 100644 --- a/apps/pcmbuf.c +++ b/apps/pcmbuf.c @@ -34,6 +34,7 @@ #include "buffer.h" #include "settings.h" #include "audio.h" +#include "voice_thread.h" #include "dsp.h" #include "thread.h" @@ -251,15 +252,21 @@ static inline void pcmbuf_add_chunk(void) #ifdef HAVE_PRIORITY_SCHEDULING static void boost_codec_thread(bool boost) { + /* Keep voice and codec threads at the same priority or else voice + * will starve if the codec thread's priority is boosted. */ if (boost) { if (codec_thread_priority == 0) + { codec_thread_priority = thread_set_priority( codec_thread_p, PRIORITY_REALTIME); + voice_thread_set_priority(PRIORITY_REALTIME); + } } else if (codec_thread_priority != 0) { thread_set_priority(codec_thread_p, codec_thread_priority); + voice_thread_set_priority(codec_thread_priority); codec_thread_priority = 0; } } @@ -717,6 +724,7 @@ static size_t crossfade_mix(const char *buf, size_t length) crossfade_chunk = crossfade_chunk->link; if (!crossfade_chunk) return length; + output_buf = (int16_t *)crossfade_chunk->addr; chunk_end = SKIPBYTES(output_buf, crossfade_chunk->size); } @@ -875,15 +883,18 @@ void* pcmbuf_request_buffer(int *count) } } -void* pcmbuf_request_voice_buffer(int *count, bool mix) +void * pcmbuf_request_voice_buffer(int *count) { - if (mix) + /* A get-it-to-work-for-now hack (audio status could change by + completion) */ + if (audio_status() & AUDIO_STATUS_PLAY) { - if (pcmbuf_read == NULL) + if (pcmbuf_read == NULL) { return NULL; } - else if (pcmbuf_mix_chunk || pcmbuf_read->link) + else if (pcmbuf_usage() >= 10 && pcmbuf_mix_free() >= 30 && + (pcmbuf_mix_chunk || pcmbuf_read->link)) { *count = MIN(*count, PCMBUF_MIX_CHUNK/4); return voicebuf; @@ -894,7 +905,9 @@ void* pcmbuf_request_voice_buffer(int *count, bool mix) } } else + { return pcmbuf_request_buffer(count); + } } bool pcmbuf_is_crossfade_active(void) @@ -1030,8 +1043,15 @@ int pcmbuf_mix_free(void) return 100; } -void pcmbuf_mix_voice(int count) +void pcmbuf_write_voice_complete(int count) { + /* A get-it-to-work-for-now hack (audio status could have changed) */ + if (!(audio_status() & AUDIO_STATUS_PLAY)) + { + pcmbuf_write_complete(count); + return; + } + int16_t *ibuf = (int16_t *)voicebuf; int16_t *obuf; size_t chunk_samples; @@ -1042,6 +1062,7 @@ void pcmbuf_mix_voice(int count) /* Start 1/8s into the next chunk */ pcmbuf_mix_sample = NATIVE_FREQUENCY * 4 / 16; } + if (!pcmbuf_mix_chunk) return; @@ -1053,6 +1074,7 @@ void pcmbuf_mix_voice(int count) while (count-- > 0) { int32_t sample = *ibuf++; + if (pcmbuf_mix_sample >= chunk_samples) { pcmbuf_mix_chunk = pcmbuf_mix_chunk->link; diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h index bb7da52644..06362452c0 100644 --- a/apps/pcmbuf.h +++ b/apps/pcmbuf.h @@ -67,16 +67,16 @@ void pcmbuf_set_position_callback(void (*callback)(size_t size)); size_t pcmbuf_free(void); unsigned int pcmbuf_get_latency(void); void pcmbuf_set_low_latency(bool state); +void * pcmbuf_request_buffer(int *count); void pcmbuf_write_complete(int count); -void* pcmbuf_request_buffer(int *count); -void* pcmbuf_request_voice_buffer(int *count, bool mix); +void * pcmbuf_request_voice_buffer(int *count); +void pcmbuf_write_voice_complete(int count); bool pcmbuf_is_crossfade_enabled(void); void pcmbuf_crossfade_enable(bool on_off); void pcmbuf_crossfade_enable_finished(void); int pcmbuf_usage(void); int pcmbuf_mix_free(void); void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude); -void pcmbuf_mix_voice(int count); int pcmbuf_used_descs(void); int pcmbuf_descs(void); diff --git a/apps/playback.c b/apps/playback.c index 90245e86cb..eac5307acc 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -47,6 +47,7 @@ #include "codecs.h" #include "audio.h" #include "buffering.h" +#include "voice_thread.h" #include "mp3_playback.h" #include "usb.h" #include "status.h" @@ -88,7 +89,6 @@ #define PLAYBACK_VOICE - /* default point to start buffer refill */ #define AUDIO_DEFAULT_WATERMARK (1024*512) /* amount of guess-space to allow for codecs that must hunt and peck @@ -142,9 +142,6 @@ enum { Q_CODEC_REQUEST_COMPLETE, Q_CODEC_REQUEST_FAILED, - Q_VOICE_PLAY, - Q_VOICE_STOP, - Q_CODEC_LOAD, Q_CODEC_LOAD_DISK, @@ -175,10 +172,6 @@ enum { #define CODEC_IRAM_SIZE ((size_t)0xc000) #endif -#ifndef IBSS_ATTR_VOICE_STACK -#define IBSS_ATTR_VOICE_STACK IBSS_ATTR -#endif - bool audio_is_initialized = false; /* Variables are commented with the threads that use them: * @@ -267,7 +260,6 @@ void (*track_unbuffer_callback)(struct mp3entry *id3) = NULL; static size_t buffer_margin = 0; /* Buffer margin aka anti-skip buffer (A/C-) */ /* Multiple threads */ -static void set_current_codec(int codec_idx); /* Set the watermark to trigger buffer fill (A/C) FIXME */ static void set_filebuf_watermark(int seconds, size_t max); @@ -291,68 +283,6 @@ IBSS_ATTR; static const char codec_thread_name[] = "codec"; struct thread_entry *codec_thread_p; /* For modifying thread priority later. */ -static volatile int current_codec IDATA_ATTR; /* Current codec (normal/voice) */ - -/* Voice thread */ -#ifdef PLAYBACK_VOICE - -extern struct codec_api ci_voice; - -static struct thread_entry *voice_thread_p = NULL; -static struct event_queue voice_queue NOCACHEBSS_ATTR; -static long voice_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] -IBSS_ATTR_VOICE_STACK; -static const char voice_thread_name[] = "voice codec"; - -/* Voice codec swapping control */ -extern unsigned char codecbuf[]; /* DRAM codec swap buffer */ - -#ifdef SIMULATOR -/* IRAM codec swap buffer for sim*/ -static unsigned char sim_iram[CODEC_IRAM_SIZE]; -#undef CODEC_IRAM_ORIGIN -#define CODEC_IRAM_ORIGIN sim_iram -#endif - -/* iram_buf and dram_buf are either both NULL or both non-NULL */ -/* Pointer to IRAM buffer for codec swapping */ -static unsigned char *iram_buf = NULL; -/* Pointer to DRAM buffer for codec swapping */ -static unsigned char *dram_buf = NULL; -/* Parity of swap_codec calls - needed because one codec swapping itself in - automatically swaps in the other and the swap when unlocking should not - happen if the parity is even. - */ -static bool swap_codec_parity NOCACHEBSS_ATTR = false; /* true=odd, false=even */ -/* Locking to control which codec (normal/voice) is running */ -static struct semaphore sem_codecthread NOCACHEBSS_ATTR; -static struct event event_codecthread NOCACHEBSS_ATTR; - -/* Voice state */ -static volatile bool voice_thread_start = false; /* Triggers voice playback (A/V) */ -static volatile bool voice_is_playing NOCACHEBSS_ATTR = false; /* Is voice currently playing? (V) */ -static volatile bool voice_codec_loaded NOCACHEBSS_ATTR = false; /* Is voice codec loaded (V/A-) */ -static unsigned char *voicebuf = NULL; -static size_t voice_remaining = 0; - -#ifdef IRAM_STEAL -/* Voice IRAM has been stolen for other use */ -static bool voice_iram_stolen = false; -#endif - -static void (*voice_getmore)(unsigned char** start, size_t* size) = NULL; - -struct voice_info { - void (*callback)(unsigned char **start, size_t* size); - size_t size; - unsigned char *buf; -}; -static void voice_thread(void); -static void voice_stop(void); - -#endif /* PLAYBACK_VOICE */ - - /* --- Helper functions --- */ static struct mp3entry *bufgetid3(int handle_id) @@ -415,71 +345,33 @@ static bool clear_track_info(struct track_info *track) /* --- External interfaces --- */ -void mp3_play_data(const unsigned char* start, int size, - void (*get_more)(unsigned char** start, size_t* size)) -{ -#ifdef PLAYBACK_VOICE - static struct voice_info voice_clip; - voice_clip.callback = get_more; - voice_clip.buf = (unsigned char*)start; - voice_clip.size = size; - LOGFQUEUE("mp3 > voice Q_VOICE_STOP"); - queue_post(&voice_queue, Q_VOICE_STOP, 0); - LOGFQUEUE("mp3 > voice Q_VOICE_PLAY"); - queue_post(&voice_queue, Q_VOICE_PLAY, (intptr_t)&voice_clip); - voice_thread_start = true; - trigger_cpu_boost(); -#else - (void) start; - (void) size; - (void) get_more; -#endif -} - -void mp3_play_stop(void) -{ -#ifdef PLAYBACK_VOICE - queue_remove_from_head(&voice_queue, Q_VOICE_STOP); - LOGFQUEUE("mp3 > voice Q_VOICE_STOP"); - queue_post(&voice_queue, Q_VOICE_STOP, 1); -#endif -} - -void mp3_play_pause(bool play) -{ - /* a dummy */ - (void)play; -} - -bool mp3_is_playing(void) -{ -#ifdef PLAYBACK_VOICE - return voice_is_playing; -#else - return false; -#endif -} - -/* If voice could be swapped out - wait for it to return - * Used by buffer claming functions. - */ -static void wait_for_voice_swap_in(void) -{ -#ifdef PLAYBACK_VOICE - if (NULL == iram_buf) - return; - - event_wait(&event_codecthread, STATE_NONSIGNALED); -#endif /* PLAYBACK_VOICE */ -} - /* This sends a stop message and the audio thread will dump all it's subsequenct messages */ -static void audio_hard_stop(void) +void audio_hard_stop(void) { /* Stop playback */ LOGFQUEUE("audio >| audio Q_AUDIO_STOP: 1"); queue_send(&audio_queue, Q_AUDIO_STOP, 1); +#ifdef PLAYBACK_VOICE + voice_stop(); +#endif +} + +bool audio_restore_playback(int type) +{ + switch (type) + { + case AUDIO_WANT_PLAYBACK: + if (buffer_state != BUFFER_STATE_INITIALIZED) + audio_reset_buffer(); + return true; + case AUDIO_WANT_VOICE: + if (buffer_state == BUFFER_STATE_TRASHED) + audio_reset_buffer(); + return true; + default: + return false; + } } unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size) @@ -489,10 +381,6 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size) if (audio_is_initialized) { audio_hard_stop(); - wait_for_voice_swap_in(); -#ifdef PLAYBACK_VOICE - voice_stop(); -#endif } /* else buffer_state will be BUFFER_STATE_TRASHED at this point */ @@ -543,68 +431,15 @@ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size) return buf; } -#ifdef IRAM_STEAL -void audio_iram_steal(void) -{ - /* We need to stop audio playback in order to use codec IRAM */ - audio_hard_stop(); - -#ifdef PLAYBACK_VOICE - if (NULL != iram_buf) - { - /* Can't already be stolen */ - if (voice_iram_stolen) - return; - - /* Must wait for voice to be current again if it is swapped which - would cause the caller's buffer to get clobbered when voice locks - and runs - we'll wait for it to lock and yield again then make sure - the ride has come to a complete stop */ - wait_for_voice_swap_in(); - voice_stop(); - - /* Save voice IRAM but just memcpy - safe to do here since voice - is current and no audio codec is loaded */ - memcpy(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE); - voice_iram_stolen = true; - } - else - { - /* Nothing much to do if no voice */ - voice_iram_stolen = false; - } -#endif -} -#endif /* IRAM_STEAL */ - #ifdef HAVE_RECORDING unsigned char *audio_get_recording_buffer(size_t *buffer_size) { - /* Don't allow overwrite of voice swap area or we'll trash the - swapped-out voice codec but can use whole thing if none */ - unsigned char *end; - - /* Stop audio and voice. Wait for voice to swap in and be clear - of pending events to ensure trouble-free operation of encoders */ + /* Stop audio, voice and obtain all available buffer space */ audio_hard_stop(); - wait_for_voice_swap_in(); -#ifdef PLAYBACK_VOICE - voice_stop(); -#endif talk_buffer_steal(); -#ifdef PLAYBACK_VOICE - /* If no dram_buf, swap space not used and recording gets more - memory. Codec swap areas will remain unaffected by the next init - since they're allocated at the end of the buffer and their sizes - don't change between calls */ - end = dram_buf; - if (NULL == end) -#endif /* PLAYBACK_VOICE */ - end = audiobufend; - + unsigned char *end = audiobufend; buffer_state = BUFFER_STATE_TRASHED; - *buffer_size = end - audiobuf; return (unsigned char *)audiobuf; @@ -952,137 +787,6 @@ void audio_set_crossfade(int enable) } /* --- Routines called from multiple threads --- */ -static void set_current_codec(int codec_idx) -{ - current_codec = codec_idx; - dsp_configure(DSP_SWITCH_CODEC, codec_idx); -} - -#ifdef PLAYBACK_VOICE -static void swap_codec(void) -{ - int my_codec; - - /* Swap nothing if no swap buffers exist */ - if (dram_buf == NULL) - { - logf("swap: no swap buffers"); - return; - } - - my_codec = current_codec; - - logf("swapping out codec: %d", my_codec); - - /* Invert this when a codec thread enters and leaves */ - swap_codec_parity = !swap_codec_parity; - - /* If this is true, an odd number of calls has occurred and there's - no codec thread waiting to swap us out when it locks and runs. This - occurs when playback is stopped or when just starting playback and - the audio thread is loading a codec; parities should always be even - on entry when a thread calls this during playback */ - if (swap_codec_parity) - { - /* Save our current IRAM and DRAM */ -#ifdef IRAM_STEAL - if (voice_iram_stolen) - { - logf("swap: iram restore"); - voice_iram_stolen = false; - /* Don't swap trashed data into buffer as the voice IRAM will - already be swapped out - should _always_ be the case if - voice_iram_stolen is true since the voice has been swapped - in beforehand */ - if (my_codec == CODEC_IDX_VOICE) - { - logf("voice iram already swapped"); - goto skip_iram_swap; - } - } -#endif - - memswap128(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE); - -#ifdef IRAM_STEAL - skip_iram_swap: -#endif - - memswap128(dram_buf, codecbuf, CODEC_SIZE); - /* No cache invalidation needed; it will be done in codec_load_ram - or we won't be here otherwise */ - } - - /* Release my semaphore */ - semaphore_release(&sem_codecthread); - logf("unlocked: %d", my_codec); - - /* Wait for other codec */ - event_wait(&event_codecthread, - (my_codec == CODEC_IDX_AUDIO) ? STATE_NONSIGNALED : STATE_SIGNALED); - - /* Wait for other codec to unlock */ - logf("waiting for lock: %d", my_codec); - semaphore_wait(&sem_codecthread); - - /* Take control */ - set_current_codec(my_codec); - event_set_state(&event_codecthread, - (my_codec == CODEC_IDX_AUDIO) ? STATE_SIGNALED : STATE_NONSIGNALED); - - /* Reload our IRAM and DRAM */ - memswap128(iram_buf, CODEC_IRAM_ORIGIN, CODEC_IRAM_SIZE); - memswap128(dram_buf, codecbuf, CODEC_SIZE); - invalidate_icache(); - - /* Flip parity again */ - swap_codec_parity = !swap_codec_parity; - - logf("resuming codec: %d", my_codec); -} - -/* This function is meant to be used by the buffer stealing functions to - ensure the codec is no longer active and so voice will be swapped-in - before it is called */ -static void voice_stop(void) -{ - /* Must have a voice codec loaded or we'll hang forever here */ - if (!voice_codec_loaded) - return; - - talk_force_shutup(); - - /* Loop until voice empties it's queue, stops and picks up on the new - track; the voice thread must be stopped and waiting for messages - outside the codec */ - while (voice_is_playing || !queue_empty(&voice_queue) || - ci_voice.new_track) - yield(); - - if (!playing) - pcmbuf_play_stop(); -} /* voice_stop */ - -/* Is voice still speaking */ -/* Unfortunately only reliable when music is not also playing. */ -static bool is_voice_speaking(void) -{ - return is_voice_queued() - || voice_is_playing - || (!playing && pcm_is_playing()); -} - -#endif /* PLAYBACK_VOICE */ - -/* Wait for voice to finish speaking. */ -/* Also only reliable when music is not also playing. */ -void voice_wait(void) -{ -#ifdef PLAYBACK_VOICE - while (is_voice_speaking()) - sleep(HZ/10); -#endif -} static void set_filebuf_watermark(int seconds, size_t max) { @@ -1126,303 +830,6 @@ const char * get_codec_filename(int cod_spec) return fname; } /* get_codec_filename */ - -/* --- Voice thread --- */ - -#ifdef PLAYBACK_VOICE - -static bool voice_pcmbuf_insert_callback( - const void *ch1, const void *ch2, int count) -{ - const char *src[2] = { ch1, ch2 }; - - while (count > 0) - { - int out_count = dsp_output_count(count); - int inp_count; - char *dest; - - while ((dest = pcmbuf_request_voice_buffer( - &out_count, playing)) == NULL) - { - if (playing && audio_codec_loaded) - swap_codec(); - else - yield(); - } - - /* Get the real input_size for output_size bytes, guarding - * against resampling buffer overflows. */ - inp_count = dsp_input_count(out_count); - - if (inp_count <= 0) - return true; - - /* Input size has grown, no error, just don't write more than length */ - if (inp_count > count) - inp_count = count; - - out_count = dsp_process(dest, src, inp_count); - - if (out_count <= 0) - return true; - - if (playing) - { - pcmbuf_mix_voice(out_count); - if ((pcmbuf_usage() < 10 || pcmbuf_mix_free() < 30) && - audio_codec_loaded) - swap_codec(); - } - else - pcmbuf_write_complete(out_count); - - count -= inp_count; - } - - return true; -} /* voice_pcmbuf_insert_callback */ - -static void* voice_get_memory_callback(size_t *size) -{ - /* Voice should have no use for this. If it did, we'd have to - swap the malloc buffer as well. */ - *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 void voice_configure_callback(int setting, intptr_t value) -{ - if (!dsp_configure(setting, value)) - { - logf("Illegal key:%d", setting); - } -} - -static size_t voice_filebuf_callback(void *ptr, size_t size) -{ - (void)ptr; - (void)size; - - return 0; -} - -/* Handle Q_VOICE_STOP and part of SYS_USB_CONNECTED */ -static bool voice_on_voice_stop(bool aborting, size_t *realsize) -{ - if (aborting && !playing) - { - /* Aborting: Slight hack - flush PCM buffer if - only being used for voice */ - pcmbuf_play_stop(); - } - - if (voice_is_playing) - { - /* Clear the current buffer */ - voice_is_playing = false; - voice_getmore = NULL; - voice_remaining = 0; - voicebuf = NULL; - - /* Cancel any automatic boost if no more clips requested. */ - if (!playing || !voice_thread_start) - sleep(0); - - /* Force the codec to think it's changing tracks */ - ci_voice.new_track = 1; - - *realsize = 0; - return true; /* Yes, change tracks */ - } - - return false; -} - -static void* voice_request_buffer_callback(size_t *realsize, size_t reqsize) -{ - struct queue_event ev; - - if (ci_voice.new_track) - { - *realsize = 0; - return NULL; - } - - while (1) - { - if (voice_is_playing || playing) - { - queue_wait_w_tmo(&voice_queue, &ev, 0); - if (!voice_is_playing && 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) - { - if (audio_codec_loaded) - swap_codec(); - yield(); - } - break; - -#ifdef AUDIO_HAVE_RECORDING - case Q_ENCODER_RECORD: - LOGFQUEUE("voice < Q_ENCODER_RECORD"); - swap_codec(); - break; -#endif - - case Q_VOICE_STOP: - LOGFQUEUE("voice < Q_VOICE_STOP"); - if (voice_on_voice_stop(ev.data, realsize)) - return NULL; - break; - - case Q_VOICE_PLAY: - LOGFQUEUE("voice < Q_VOICE_PLAY"); - if (!voice_is_playing) - { - /* Set up new voice data */ - struct voice_info *voice_data; -#ifdef IRAM_STEAL - if (voice_iram_stolen) - { - /* Voice is the first to run again and is currently - loaded */ - logf("voice: iram restore"); - memcpy(CODEC_IRAM_ORIGIN, iram_buf, CODEC_IRAM_SIZE); - voice_iram_stolen = false; - } -#endif - /* Must reset the buffer before any playback begins if - needed */ - if (buffer_state == BUFFER_STATE_TRASHED) - audio_reset_buffer(); - - voice_is_playing = true; - trigger_cpu_boost(); - voice_data = (struct voice_info *)ev.data; - voice_remaining = voice_data->size; - voicebuf = voice_data->buf; - voice_getmore = voice_data->callback; - } - goto voice_play_clip; /* To exit both switch and while */ - - case SYS_TIMEOUT: - LOGFQUEUE_SYS_TIMEOUT("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, &voice_remaining); - - /* If this clip is done */ - if (voice_remaining == 0) - { - 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) -{ - logf("Loading voice codec"); - voice_codec_loaded = true; - semaphore_wait(&sem_codecthread); - event_set_state(&event_codecthread, STATE_NONSIGNALED); - set_current_codec(CODEC_IDX_VOICE); - dsp_configure(DSP_RESET, 0); - voice_remaining = 0; - voice_getmore = NULL; - - /* FIXME: If we being starting the voice thread without reboot, the - voice_queue could be full of old stuff and we must flush it. */ - codec_load_file(get_codec_filename(AFMT_MPA_L3), &ci_voice); - - logf("Voice codec finished"); - voice_codec_loaded = false; - voice_thread_p = NULL; - semaphore_release(&sem_codecthread); -} /* voice_thread */ - -#endif /* PLAYBACK_VOICE */ - /* --- Codec thread --- */ static bool codec_pcmbuf_insert_callback( const void *ch1, const void *ch2, int count) @@ -1431,7 +838,7 @@ static bool codec_pcmbuf_insert_callback( while (count > 0) { - int out_count = dsp_output_count(count); + int out_count = dsp_output_count(ci.dsp, count); int inp_count; char *dest; @@ -1448,7 +855,7 @@ static bool codec_pcmbuf_insert_callback( /* Get the real input_size for output_size bytes, guarding * against resampling buffer overflows. */ - inp_count = dsp_input_count(out_count); + inp_count = dsp_input_count(ci.dsp, out_count); if (inp_count <= 0) return true; @@ -1457,23 +864,13 @@ static bool codec_pcmbuf_insert_callback( if (inp_count > count) inp_count = count; - out_count = dsp_process(dest, src, inp_count); + out_count = dsp_process(ci.dsp, dest, src, inp_count); if (out_count <= 0) return true; pcmbuf_write_complete(out_count); -#ifdef PLAYBACK_VOICE - if ((voice_is_playing || voice_thread_start) - && pcm_is_playing() && voice_codec_loaded && - pcmbuf_usage() > 30 && pcmbuf_mix_free() > 80) - { - voice_thread_start = false; - swap_codec(); - } -#endif - count -= inp_count; } @@ -1690,7 +1087,7 @@ static void codec_seek_complete_callback(void) { /* If this is not a seamless seek, clear the buffer */ pcmbuf_play_stop(); - dsp_configure(DSP_FLUSH, 0); + dsp_configure(ci.dsp, DSP_FLUSH, 0); /* If playback was not 'deliberately' paused, unpause now */ if (!paused) @@ -1721,7 +1118,8 @@ static void codec_configure_callback(int setting, intptr_t value) break; default: - if (!dsp_configure(setting, value)) { logf("Illegal key:%d", setting); } + if (!dsp_configure(ci.dsp, setting, value)) + { logf("Illegal key:%d", setting); } } } @@ -1886,23 +1284,8 @@ static void codec_thread(void) LOGFQUEUE("codec < Q_CODEC_LOAD_DISK"); queue_reply(&codec_queue, 1); audio_codec_loaded = true; -#ifdef PLAYBACK_VOICE - /* Don't sent messages to voice codec if it's already swapped - out or it will never get this */ - if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE) - { - LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); - queue_post(&voice_queue, Q_AUDIO_PLAY, 0); - } - semaphore_wait(&sem_codecthread); - event_set_state(&event_codecthread, STATE_SIGNALED); -#endif - set_current_codec(CODEC_IDX_AUDIO); ci.stop_codec = false; status = codec_load_file((const char *)ev.data, &ci); -#ifdef PLAYBACK_VOICE - semaphore_release(&sem_codecthread); -#endif break; case Q_CODEC_LOAD: @@ -1920,43 +1303,17 @@ static void codec_thread(void) } audio_codec_loaded = true; -#ifdef PLAYBACK_VOICE - if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE) - { - LOGFQUEUE("codec > voice Q_AUDIO_PLAY"); - queue_post(&voice_queue, Q_AUDIO_PLAY, 0); - } - semaphore_wait(&sem_codecthread); - event_set_state(&event_codecthread, STATE_SIGNALED); -#endif - set_current_codec(CODEC_IDX_AUDIO); ci.stop_codec = false; status = codec_load_buf(CUR_TI->codec_hid, &ci); -#ifdef PLAYBACK_VOICE - semaphore_release(&sem_codecthread); -#endif break; #ifdef AUDIO_HAVE_RECORDING case Q_ENCODER_LOAD_DISK: LOGFQUEUE("codec < Q_ENCODER_LOAD_DISK"); audio_codec_loaded = false; /* Not audio codec! */ -#ifdef PLAYBACK_VOICE - if (voice_codec_loaded && current_codec == CODEC_IDX_VOICE) - { - LOGFQUEUE("codec > voice Q_ENCODER_RECORD"); - queue_post(&voice_queue, Q_ENCODER_RECORD, 0); - } - semaphore_wait(&sem_codecthread); - event_set_state(&event_codecthread, STATE_SIGNALED); -#endif logf("loading encoder"); - set_current_codec(CODEC_IDX_AUDIO); ci.stop_encoder = false; status = codec_load_file((const char *)ev.data, &ci); -#ifdef PLAYBACK_VOICE - semaphore_release(&sem_codecthread); -#endif logf("encoder stopped"); break; #endif /* AUDIO_HAVE_RECORDING */ @@ -2341,13 +1698,8 @@ static bool audio_load_track(int offset, bool start_play) /* Set default values */ if (start_play) { - int last_codec = current_codec; - - set_current_codec(CODEC_IDX_AUDIO); buf_set_watermark(AUDIO_DEFAULT_WATERMARK); - dsp_configure(DSP_RESET, 0); - set_current_codec(last_codec); - + dsp_configure(ci.dsp, DSP_RESET, 0); track_changed = true; playlist_update_resume_info(audio_current_track()); } @@ -3001,67 +2353,7 @@ static void audio_reset_buffer(void) filebuf = malloc_buf + MALLOC_BUFSIZE; /* filebuf line align implied */ filebuflen = audiobufend - filebuf; - /* Allow for codec swap space at end of audio buffer */ - if (talk_voice_required()) - { - /* Layout of swap buffer: - * #ifdef IRAM_STEAL (dedicated iram_buf): - * |iram_buf|...audiobuf...|dram_buf|audiobufend - * #else: - * audiobuf...|dram_buf|iram_buf|audiobufend - */ -#ifdef PLAYBACK_VOICE - /* Check for an absolutely nasty situation which should never, - ever happen - frankly should just panic */ - if (voice_codec_loaded && current_codec != CODEC_IDX_VOICE) - { - logf("buffer reset with voice swapped"); - } - /* line align length which line aligns the calculations below since - all sizes are also at least line aligned - needed for memswap128 */ - filebuflen &= ~15; -#ifdef IRAM_STEAL - filebuflen -= CODEC_SIZE; -#else - filebuflen -= CODEC_SIZE + CODEC_IRAM_SIZE; -#endif - /* Allocate buffers for swapping voice <=> audio */ - /* If using IRAM for plugins voice IRAM swap buffer must be dedicated - and out of the way of buffer usage or else a call to audio_get_buffer - and subsequent buffer use might trash the swap space. A plugin - initializing IRAM after getting the full buffer would present similar - problem. Options include: failing the request if the other buffer - has been obtained already or never allowing use of the voice IRAM - buffer within the audio buffer. Using buffer_alloc basically - implements the second in a more convenient way. */ - dram_buf = filebuf + filebuflen; - -#ifdef IRAM_STEAL - /* Allocate voice IRAM swap buffer once */ - if (iram_buf == NULL) - { - iram_buf = buffer_alloc(CODEC_IRAM_SIZE); - /* buffer_alloc moves audiobuf; this is safe because only the end - * has been touched so far in this function and the address of - * filebuf + filebuflen is not changed */ - malloc_buf += CODEC_IRAM_SIZE; - filebuf += CODEC_IRAM_SIZE; - filebuflen -= CODEC_IRAM_SIZE; - } -#else - /* Allocate iram_buf after dram_buf */ - iram_buf = dram_buf + CODEC_SIZE; -#endif /* IRAM_STEAL */ -#endif /* PLAYBACK_VOICE */ - } - else - { -#ifdef PLAYBACK_VOICE - /* No swap buffers needed */ - iram_buf = NULL; - dram_buf = NULL; -#endif - } + filebuflen &= ~15; /* Subtract whatever the pcm buffer says it used plus the guard buffer */ filebuflen -= pcmbuf_init(filebuf + filebuflen) + GUARD_BUFSIZE; @@ -3090,16 +2382,6 @@ static void audio_reset_buffer(void) logf("gbufe: %08X", (unsigned)(filebuf + filebuflen + GUARD_BUFSIZE)); logf("pcmb: %08X", (unsigned)pcmbuf); logf("pcmbe: %08X", (unsigned)(pcmbuf + pcmbufsize)); - if (dram_buf) - { - logf("dramb: %08X", (unsigned)dram_buf); - logf("drambe: %08X", (unsigned)(dram_buf + CODEC_SIZE)); - } - if (iram_buf) - { - logf("iramb: %08X", (unsigned)iram_buf); - logf("irambe: %08X", (unsigned)(iram_buf + CODEC_IRAM_SIZE)); - } } #endif } @@ -3110,21 +2392,6 @@ static void audio_thread(void) pcm_postinit(); -#ifdef PLAYBACK_VOICE - /* Unlock semaphore that init stage locks before creating this thread */ - semaphore_release(&sem_codecthread); - - /* Buffers must be set up by now - should panic - really */ - if (buffer_state != BUFFER_STATE_INITIALIZED) - { - logf("audio_thread start: no buffer"); - } - - /* Have to wait for voice to load up or else the codec swap will be - invalid when an audio codec is loaded */ - wait_for_voice_swap_in(); -#endif - while (1) { queue_wait_w_tmo(&audio_queue, &ev, HZ/2); @@ -3214,7 +2481,6 @@ static void audio_thread(void) if (playing) audio_stop_playback(); #ifdef PLAYBACK_VOICE - wait_for_voice_swap_in(); voice_stop(); #endif usb_acknowledge(SYS_USB_CONNECTED_ACK); @@ -3253,11 +2519,6 @@ static void audio_test_track_changed_event(struct mp3entry *id3) */ void audio_init(void) { -#ifdef PLAYBACK_VOICE - static bool voicetagtrue = true; - static struct mp3entry id3_voice; - struct thread_entry *voice_thread_p = NULL; -#endif struct thread_entry *audio_thread_p; /* Can never do this twice */ @@ -3272,13 +2533,6 @@ void audio_init(void) /* Initialize queues before giving control elsewhere in case it likes to send messages. Thread creation will be delayed however so nothing starts running until ready if something yields such as talk_init. */ -#ifdef PLAYBACK_VOICE - /* Take ownership of lock to prevent playback of anything before audio - hardware is initialized - audio thread unlocks it after final init - stage */ - semaphore_init(&sem_codecthread, 1, 0); - event_init(&event_codecthread, EVENT_MANUAL | STATE_SIGNALED); -#endif queue_init(&audio_queue, true); queue_enable_queue_send(&audio_queue, &audio_queue_sender_list); queue_init(&codec_queue, false); @@ -3305,30 +2559,8 @@ void audio_init(void) ci.set_offset = codec_set_offset_callback; ci.configure = codec_configure_callback; ci.discard_codec = codec_discard_codec_callback; - - /* Initialize voice codec api. */ -#ifdef PLAYBACK_VOICE - memcpy(&ci_voice, &ci, sizeof(ci_voice)); - memset(&id3_voice, 0, sizeof(id3_voice)); - ci_voice.read_filebuf = voice_filebuf_callback; - ci_voice.pcmbuf_insert = voice_pcmbuf_insert_callback; - ci_voice.get_codec_memory = voice_get_memory_callback; - ci_voice.request_buffer = voice_request_buffer_callback; - ci_voice.advance_buffer = voice_advance_buffer_callback; - ci_voice.advance_buffer_loc = voice_advance_buffer_loc_callback; - ci_voice.request_next_track = voice_request_next_track_callback; - ci_voice.mp3_get_filepos = voice_mp3_get_filepos_callback; - ci_voice.seek_buffer = voice_seek_buffer_callback; - ci_voice.seek_complete = voice_do_nothing; - ci_voice.set_elapsed = voice_set_elapsed_callback; - ci_voice.set_offset = voice_set_offset_callback; - ci_voice.configure = voice_configure_callback; - ci_voice.discard_codec = voice_do_nothing; - ci_voice.taginfo_ready = &voicetagtrue; - ci_voice.id3 = &id3_voice; - id3_voice.frequency = 11200; - id3_voice.length = 1000000L; -#endif + ci.dsp = (struct dsp_config *)dsp_configure(NULL, DSP_MYDSP, + CODEC_IDX_AUDIO); /* initialize the buffer */ filebuf = audiobuf; @@ -3349,16 +2581,7 @@ void audio_init(void) IF_COP(, CPU)); #ifdef PLAYBACK_VOICE - /* TODO: Change this around when various speech codecs can be used */ - if (talk_voice_required()) - { - logf("Starting voice codec"); - queue_init(&voice_queue, false); - voice_thread_p = create_thread(voice_thread, voice_stack, - sizeof(voice_stack), CREATE_THREAD_FROZEN, - voice_thread_name - IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU)); - } + voice_thread_init(); #endif /* Set crossfade setting for next buffer init which should be about... */ @@ -3393,11 +2616,10 @@ void audio_init(void) #endif /* it's safe to let the threads run now */ - thread_thaw(codec_thread_p); #ifdef PLAYBACK_VOICE - if (voice_thread_p) - thread_thaw(voice_thread_p); + voice_thread_resume(); #endif + thread_thaw(codec_thread_p); thread_thaw(audio_thread_p); -} /* audio_init */ +} /* audio_init */ diff --git a/apps/plugin.c b/apps/plugin.c index a80e9dd86d..145d30b41b 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -457,7 +457,7 @@ static const struct plugin_api rockbox_api = { plugin_get_audio_buffer, plugin_tsr, plugin_get_current_filename, -#ifdef IRAM_STEAL +#ifdef PLUGIN_USE_IRAM plugin_iram_init, #endif #if defined(DEBUG) || defined(SIMULATOR) @@ -732,12 +732,13 @@ void* plugin_get_audio_buffer(size_t *buffer_size) #endif } -#ifdef IRAM_STEAL +#ifdef PLUGIN_USE_IRAM /* Initializes plugin IRAM */ void plugin_iram_init(char *iramstart, char *iramcopy, size_t iram_size, char *iedata, size_t iedata_size) { - audio_iram_steal(); + /* We need to stop audio playback in order to use codec IRAM */ + audio_hard_stop(); memcpy(iramstart, iramcopy, iram_size); memset(iedata, 0, iedata_size); memset(iramcopy, 0, iram_size); @@ -746,7 +747,7 @@ void plugin_iram_init(char *iramstart, char *iramcopy, size_t iram_size, flush_icache(); #endif } -#endif /* IRAM_STEAL */ +#endif /* PLUGIN_USE_IRAM */ /* The plugin wants to stay resident after leaving its main function, e.g. runs from timer or own thread. The callback is registered to later diff --git a/apps/plugin.h b/apps/plugin.h index 75d8654240..9123af44ec 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -568,7 +568,7 @@ struct plugin_api { void* (*plugin_get_audio_buffer)(size_t *buffer_size); void (*plugin_tsr)(bool (*exit_callback)(bool reenter)); char* (*plugin_get_current_filename)(void); -#ifdef IRAM_STEAL +#ifdef PLUGIN_USE_IRAM void (*plugin_iram_init)(char *iramstart, char *iramcopy, size_t iram_size, char *iedata, size_t iedata_size); #endif @@ -674,7 +674,7 @@ extern unsigned char plugin_end_addr[]; NULL, NULL, plugin_start }; #endif /* SIMULATOR */ -#ifdef USE_IRAM +#ifdef PLUGIN_USE_IRAM /* Declare IRAM variables */ #define PLUGIN_IRAM_DECLARE \ extern char iramcopy[]; \ @@ -689,13 +689,13 @@ extern unsigned char plugin_end_addr[]; #else #define PLUGIN_IRAM_DECLARE #define PLUGIN_IRAM_INIT(api) -#endif /* USE_IRAM */ +#endif /* PLUGIN_USE_IRAM */ #endif /* PLUGIN */ int plugin_load(const char* plugin, void* parameter); void* plugin_get_buffer(size_t *buffer_size); void* plugin_get_audio_buffer(size_t *buffer_size); -#ifdef IRAM_STEAL +#ifdef PLUGIN_USE_IRAM void plugin_iram_init(char *iramstart, char *iramcopy, size_t iram_size, char *iedata, size_t iedata_size); #endif diff --git a/apps/talk.c b/apps/talk.c index 53729ccd7d..332a10f820 100644 --- a/apps/talk.c +++ b/apps/talk.c @@ -56,8 +56,6 @@ | | filebuf | |------------ | | audio - | |------------ - | | codec swap audiobufend----------+-----------+------------ SWCODEC allocates dedicated buffers, MASCODEC reuses audiobuf. */ @@ -628,7 +626,9 @@ int talk_file(const char* filename, bool enqueue) { int fd; int size; +#if CONFIG_CODEC != SWCODEC struct mp3entry info; +#endif if (talk_temp_disable_count > 0) return -1; /* talking has been disabled */ @@ -640,10 +640,12 @@ int talk_file(const char* filename, bool enqueue) if (p_thumbnail == NULL || size_for_thumbnail <= 0) return -1; +#if CONFIG_CODEC != SWCODEC if(mp3info(&info, filename)) /* use this to find real start */ { return 0; /* failed to open, or invalid */ } +#endif fd = open(filename, O_RDONLY); if (fd < 0) /* failed to open */ @@ -651,14 +653,16 @@ int talk_file(const char* filename, bool enqueue) return 0; } +#if CONFIG_CODEC != SWCODEC lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ +#endif size = read(fd, p_thumbnail, size_for_thumbnail); close(fd); /* ToDo: find audio, skip ID headers and trailers */ - if (size != 0 && size != size_for_thumbnail) /* Don't play missing or truncated clips */ + if (size > 0 && size != size_for_thumbnail) /* Don't play missing or truncated clips */ { #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) bitswap(p_thumbnail, size); diff --git a/apps/talk.h b/apps/talk.h index fcb365be75..adf4155836 100644 --- a/apps/talk.h +++ b/apps/talk.h @@ -27,7 +27,7 @@ #include #include "time.h" -#define VOICE_VERSION 300 /* 3.00 - if you change this, change it in voicefont too */ +#define VOICE_VERSION 400 /* 4.00 - if you change this, change it in voicefont too */ enum { /* See array "unit_voiced" in talk.c function "talk_value" */ diff --git a/apps/voice_thread.c b/apps/voice_thread.c new file mode 100644 index 0000000000..8792d1c752 --- /dev/null +++ b/apps/voice_thread.c @@ -0,0 +1,444 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "system.h" +#include "thread.h" +#include "logf.h" +#include "voice_thread.h" +#include "talk.h" +#include "dsp.h" +#include "audio.h" +#include "pcmbuf.h" +#include "codecs/libspeex/speex/speex.h" + +/* Define any of these as "1" to log regular and/or timeout messages */ +#define VOICE_LOGQUEUES 0 +#define VOICE_LOGQUEUES_SYS_TIMEOUT 0 + +#if VOICE_LOGQUEUES +#define LOGFQUEUE logf +#else +#define LOGFQUEUE(...) +#endif + +#if VOICE_LOGQUEUES_SYS_TIMEOUT +#define LOGFQUEUE_SYS_TIMEOUT logf +#else +#define LOGFQUEUE_SYS_TIMEOUT(...) +#endif + +#ifndef IBSS_ATTR_VOICE_STACK +#define IBSS_ATTR_VOICE_STACK IBSS_ATTR +#endif + +#define VOICE_FRAME_SIZE 320 /* Samples / frame */ +#define VOICE_SAMPLE_RATE 16000 /* Sample rate in HZ */ +#define VOICE_SAMPLE_DEPTH 16 /* Sample depth in bits */ + +/* Voice thread variables */ +static struct thread_entry *voice_thread_p = NULL; +static long voice_stack[0x740/sizeof(long)] IBSS_ATTR_VOICE_STACK; +static const char voice_thread_name[] = "voice"; + +/* Voice thread synchronization objects */ +static struct event_queue voice_queue NOCACHEBSS_ATTR; +static struct mutex voice_mutex NOCACHEBSS_ATTR; +static struct event voice_event NOCACHEBSS_ATTR; +static struct queue_sender_list voice_queue_sender_list NOCACHEBSS_ATTR; + +/* Buffer for decoded samples */ +static spx_int16_t voice_output_buf[VOICE_FRAME_SIZE] CACHEALIGN_ATTR; + +enum voice_thread_states +{ + TSTATE_STOPPED = 0, /* Voice thread is stopped and awaiting commands */ + TSTATE_DECODE, /* Voice is decoding a clip */ + TSTATE_BUFFER_INSERT, /* Voice is sending decoded audio to PCM */ +}; + +enum voice_thread_messages +{ + Q_VOICE_NULL = 0, /* A message for thread sync - no effect on state */ + Q_VOICE_PLAY, /* Play a clip */ + Q_VOICE_STOP, /* Stop current clip */ + Q_VOICE_STATE, /* Query playing state */ +}; + +/* Structure to store clip data callback info */ +struct voice_info +{ + pcm_more_callback_type get_more; /* Callback to get more clips */ + unsigned char *start; /* Start of clip */ + ssize_t size; /* Size of clip */ +}; + +/* Private thread data for its current state that must be passed to its + * internal functions */ +struct voice_thread_data +{ + int state; /* Thread state (TSTATE_*) */ + struct queue_event ev; /* Last queue event pulled from queue */ + void *st; /* Decoder instance */ + SpeexBits bits; /* Bit cursor */ + struct dsp_config *dsp; /* DSP used for voice output */ + struct voice_info vi; /* Copy of clip data */ + const char *src[2]; /* Current output buffer pointers */ + int lookahead; /* Number of samples to drop at start of clip */ + int count; /* Count of samples remaining to send to PCM */ +}; + +/* Audio playback is in a playing state? */ +static inline bool playback_is_playing(void) +{ + return (audio_status() & AUDIO_STATUS_PLAY) != 0; +} + +/* Stop any current clip and start playing a new one */ +void mp3_play_data(const unsigned char* start, int size, + pcm_more_callback_type get_more) +{ + /* Shared struct to get data to the thread - once it replies, it has + * safely cached it in its own private data */ + static struct voice_info voice_clip NOCACHEBSS_ATTR; + + if (get_more != NULL && start != NULL && size > 0) + { + mutex_lock(&voice_mutex); + + voice_clip.get_more = get_more; + voice_clip.start = (unsigned char *)start; + voice_clip.size = size; + LOGFQUEUE("mp3 >| voice Q_VOICE_PLAY"); + queue_send(&voice_queue, Q_VOICE_PLAY, (intptr_t)&voice_clip); + + mutex_unlock(&voice_mutex); + } +} + +/* Stop current voice clip from playing */ +void mp3_play_stop(void) +{ + mutex_lock(&voice_mutex); /* Sync against voice_stop */ + + LOGFQUEUE("mp3 >| voice Q_VOICE_STOP: 1"); + queue_send(&voice_queue, Q_VOICE_STOP, 1); + + mutex_unlock(&voice_mutex); +} + +void mp3_play_pause(bool play) +{ + /* a dummy */ + (void)play; +} + +/* Tell is voice is still in a playing state */ +bool mp3_is_playing(void) +{ + /* TODO: Implement a timeout or state query function for event objects */ + LOGFQUEUE("mp3 >| voice Q_VOICE_STATE"); + int state = queue_send(&voice_queue, Q_VOICE_STATE, 0); + return state != TSTATE_STOPPED; +} + +/* This function is meant to be used by the buffer request functions to + ensure the codec is no longer active */ +void voice_stop(void) +{ + mutex_lock(&voice_mutex); + + /* Stop the output and current clip */ + mp3_play_stop(); + + /* Careful if using sync objects in talk.c - make sure locking order is + * observed with one or the other always granted first */ + + /* Unqueue all future clips */ + talk_force_shutup(); + + mutex_unlock(&voice_mutex); +} /* voice_stop */ + +/* Wait for voice to finish speaking. */ +void voice_wait(void) +{ + /* NOTE: One problem here is that we can't tell if another thread started a + * new clip by the time we wait. This should be resolvable if conditions + * ever require knowing the very clip you requested has finished. */ + event_wait(&voice_event, STATE_SIGNALED); +} + +/* Initialize voice thread data that must be valid upon starting and the + * setup the DSP parameters */ +static void voice_data_init(struct voice_thread_data *td) +{ + td->state = TSTATE_STOPPED; + td->dsp = (struct dsp_config *)dsp_configure(NULL, DSP_MYDSP, + CODEC_IDX_VOICE); + + dsp_configure(td->dsp, DSP_RESET, 0); + dsp_configure(td->dsp, DSP_SET_FREQUENCY, VOICE_SAMPLE_RATE); + dsp_configure(td->dsp, DSP_SET_SAMPLE_DEPTH, VOICE_SAMPLE_DEPTH); + dsp_configure(td->dsp, DSP_SET_STEREO_MODE, STEREO_MONO); +} + +/* Voice thread message processing */ +static void voice_message(struct voice_thread_data *td) +{ + while (1) + { + switch (td->ev.id) + { + case Q_VOICE_PLAY: + LOGFQUEUE("voice < Q_VOICE_PLAY"); + /* Put up a block for completion signal */ + event_set_state(&voice_event, STATE_NONSIGNALED); + + /* Copy the clip info */ + td->vi = *(struct voice_info *)td->ev.data; + + /* Be sure audio buffer is initialized */ + audio_restore_playback(AUDIO_WANT_VOICE); + + /* We need nothing more from the sending thread - let it run */ + queue_reply(&voice_queue, 1); + + if (td->state == TSTATE_STOPPED) + { + /* Boost CPU now */ + trigger_cpu_boost(); + } + else if (!playback_is_playing()) + { + /* Just voice, stop any clip still playing */ + pcmbuf_play_stop(); + } + + /* Clean-start the decoder */ + td->st = speex_decoder_init(&speex_wb_mode); + + /* Make bit buffer use our own buffer */ + speex_bits_set_bit_buffer(&td->bits, td->vi.start, td->vi.size); + speex_decoder_ctl(td->st, SPEEX_GET_LOOKAHEAD, &td->lookahead); + + td->state = TSTATE_DECODE; + return; + + case Q_VOICE_STOP: + LOGFQUEUE("voice < Q_VOICE_STOP: %d", ev.data); + + if (td->ev.data != 0 && !playback_is_playing()) + { + /* If not playing, it's just voice so stop pcm playback */ + pcmbuf_play_stop(); + } + + /* Cancel boost */ + sleep(0); + + td->state = TSTATE_STOPPED; + event_set_state(&voice_event, STATE_SIGNALED); + break; + + case Q_VOICE_STATE: + LOGFQUEUE("voice < Q_VOICE_STATE"); + queue_reply(&voice_queue, td->state); + + if (td->state == TSTATE_STOPPED) + break; /* Not in a playback state */ + + return; + + default: + /* Default messages get a reply and thread continues with no + * state transition */ + LOGFQUEUE("voice < default"); + + if (td->state == TSTATE_STOPPED) + break; /* Not in playback state */ + + queue_reply(&voice_queue, 0); + return; + } + + queue_wait(&voice_queue, &td->ev); + } +} + +/* Voice thread entrypoint */ +static void voice_thread(void) +{ + struct voice_thread_data td; + + voice_data_init(&td); + + goto message_wait; + + while (1) + { + td.state = TSTATE_DECODE; + + if (!queue_empty(&voice_queue)) + { + message_wait: + queue_wait(&voice_queue, &td.ev); + + message_process: + voice_message(&td); + + /* Branch to initial start point or branch back to previous + * operation if interrupted by a message */ + switch (td.state) + { + case TSTATE_DECODE: goto voice_decode; + case TSTATE_BUFFER_INSERT: goto buffer_insert; + default: goto message_wait; + } + } + + voice_decode: + /* Check if all data was exhausted for this clip */ + if (speex_bits_remaining(&td.bits) < 8) + { + voice_error: + /* Get next clip */ + td.vi.size = 0; + + if (td.vi.get_more != NULL) + td.vi.get_more(&td.vi.start, &td.vi.size); + + if (td.vi.start != NULL && td.vi.size > 0) + { + /* Make bit buffer use our own buffer */ + speex_bits_set_bit_buffer(&td.bits, td.vi.start, td.vi.size); + speex_decoder_ctl(td.st, SPEEX_GET_LOOKAHEAD, &td.lookahead); + + yield(); + + if (!queue_empty(&voice_queue)) + goto message_wait; + else + goto voice_decode; + } + + /* If all clips are done and not playing, force pcm playback. */ + if (!pcm_is_playing()) + pcmbuf_play_start(); + + /* Synthesize a stop request */ + /* NOTE: We have no way to know when the pcm data placed in the + * buffer is actually consumed and playback has reached the end + * so until the info is available or inferred somehow, this will + * not be accurate and the stopped signal will come too soon. + * ie. You may not hear the "Shutting Down" splash even though + * it waits for voice to stop. */ + td.ev.id = Q_VOICE_STOP; + td.ev.data = 0; /* Let PCM drain by itself */ + yield(); + goto message_process; + } + + /* Decode the data */ + int status = speex_decode_int(td.st, &td.bits, voice_output_buf); + yield(); + + if (status == -2) + goto voice_error; /* error - try some more */ + + /* Output the decoded frame */ + td.count = VOICE_FRAME_SIZE - td.lookahead; + td.src[0] = (const char *)&voice_output_buf[td.lookahead]; + td.src[1] = NULL; + td.lookahead -= MIN(VOICE_FRAME_SIZE, td.lookahead); + + buffer_insert: + /* Process the PCM samples in the DSP and send out for mixing */ + td.state = TSTATE_BUFFER_INSERT; + + while (td.count > 0) + { + int out_count = dsp_output_count(td.dsp, td.count); + int inp_count; + char *dest; + + while (1) + { + if (!queue_empty(&voice_queue)) + goto message_wait; + + if ((dest = pcmbuf_request_voice_buffer(&out_count)) != NULL) + break; + + yield(); + } + + /* Get the real input_size for output_size bytes, guarding + * against resampling buffer overflows. */ + inp_count = dsp_input_count(td.dsp, out_count); + + if (inp_count <= 0) + break; + + /* Input size has grown, no error, just don't write more than + * length */ + if (inp_count > td.count) + inp_count = td.count; + + out_count = dsp_process(td.dsp, dest, td.src, inp_count); + + if (out_count <= 0) + break; + + pcmbuf_write_voice_complete(out_count); + td.count -= inp_count; + } + + yield(); + } /* end while */ +} /* voice_thread */ + +/* Initialize all synchronization objects create the thread */ +void voice_thread_init(void) +{ + logf("Starting voice thread"); + queue_init(&voice_queue, false); + queue_enable_queue_send(&voice_queue, &voice_queue_sender_list); + mutex_init(&voice_mutex); + event_init(&voice_event, STATE_SIGNALED | EVENT_MANUAL); + voice_thread_p = create_thread(voice_thread, voice_stack, + sizeof(voice_stack), CREATE_THREAD_FROZEN, + voice_thread_name IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU)); +} /* voice_thread_init */ + +/* Unfreeze the voice thread */ +void voice_thread_resume(void) +{ + logf("Thawing voice thread"); + thread_thaw(voice_thread_p); + /* Wait for initialization to complete (a very short wait until the + * voice thread is available to process messages) */ + queue_send(&voice_queue, Q_VOICE_NULL, 0); +} + +#ifdef HAVE_PRIORITY_SCHEDULING +/* Set the voice thread priority */ +void voice_thread_set_priority(int priority) +{ + thread_set_priority(voice_thread_p, priority); +} +#endif diff --git a/apps/voice_thread.h b/apps/voice_thread.h new file mode 100644 index 0000000000..72c2054317 --- /dev/null +++ b/apps/voice_thread.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef VOICE_THREAD_H +#define VOICE_THREAD_H + +void mp3_play_data(const unsigned char* start, int size, + void (*get_more)(unsigned char** start, size_t* size)); +void mp3_play_stop(void); +void mp3_play_pause(bool play); +bool mp3_is_playing(void); + +void voice_stop(void); +void voice_thread_init(void); +void voice_thread_resume(void); +void voice_thread_set_priority(int priority); + +#endif /* VOICE_THREAD_H */ diff --git a/firmware/SOURCES b/firmware/SOURCES index 9f3b490e5e..59179961ee 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -282,7 +282,6 @@ target/coldfire/crt0.S target/coldfire/memcpy-coldfire.S target/coldfire/memmove-coldfire.S target/coldfire/memset-coldfire.S -target/coldfire/memswap128-coldfire.S target/coldfire/strlen-coldfire.S #if defined(HAVE_LCD_COLOR) \ || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_PIXELFORMAT == VERTICAL_INTERLEAVED) @@ -305,7 +304,6 @@ common/strlen.c #ifndef SIMULATOR target/arm/memset-arm.S target/arm/memset16-arm.S -target/arm/memswap128-arm.S #if CONFIG_I2C == I2C_PP5024 || CONFIG_I2C == I2C_PP5020 || CONFIG_I2C == I2C_PP5002 target/arm/i2c-pp.c #elif CONFIG_I2C == I2C_PNX0101 @@ -350,7 +348,6 @@ common/memcpy.c common/memmove.c common/memset.c common/memset16.c -common/memswap128.c common/strlen.c #ifndef SIMULATOR crt0.S diff --git a/firmware/common/memswap128.c b/firmware/common/memswap128.c deleted file mode 100644 index af1fe157b6..0000000000 --- a/firmware/common/memswap128.c +++ /dev/null @@ -1,44 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2007 by Michael Sevakis - * - * All files in this archive are subject to the GNU General Public License. - * See the file COPYING in the source tree root for full license agreement. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -#include "config.h" -#include -#include - -void memswap128(void *a, void *b, size_t len) -{ - for (len >>= 4; len > 0; len--, a += 16, b += 16) - { - int32_t a0 = *((int32_t *)a + 0); - int32_t a1 = *((int32_t *)a + 1); - int32_t a2 = *((int32_t *)a + 2); - int32_t a3 = *((int32_t *)a + 3); - int32_t b0 = *((int32_t *)b + 0); - int32_t b1 = *((int32_t *)b + 1); - int32_t b2 = *((int32_t *)b + 2); - int32_t b3 = *((int32_t *)b + 3); - *((int32_t *)b + 0) = a0; - *((int32_t *)b + 1) = a1; - *((int32_t *)b + 2) = a2; - *((int32_t *)b + 3) = a3; - *((int32_t *)a + 0) = b0; - *((int32_t *)a + 1) = b1; - *((int32_t *)a + 2) = b2; - *((int32_t *)a + 3) = b3; - } -} diff --git a/firmware/export/audio.h b/firmware/export/audio.h index 468856a87a..061b2230f4 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -109,10 +109,16 @@ void audio_beep(int duration); void audio_init_playback(void); /* Required call when audio buffer is require for some other purpose */ unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size); -#ifdef IRAM_STEAL -/* Required call when codec IRAM is needed for some other purpose */ -void audio_iram_steal(void); -#endif +/* Stops audio from serving playback */ +void audio_hard_stop(void); +/* Retores the audio buffer to handle the requested playback */ +enum +{ + AUDIO_WANT_PLAYBACK = 0, + AUDIO_WANT_VOICE, +}; + +bool audio_restore_playback(int type); /* channel modes */ enum rec_channel_modes diff --git a/firmware/export/config.h b/firmware/export/config.h index 48dc3f5693..6a2c02cffd 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -383,7 +383,7 @@ #define IBSS_ATTR __attribute__ ((section(".ibss"))) #define USE_IRAM #if CONFIG_CPU != SH7034 -#define IRAM_STEAL +#define PLUGIN_USE_IRAM #endif #if defined(CPU_ARM) /* GCC quirk workaround: arm-elf-gcc treats static functions as short_call diff --git a/firmware/target/arm/memswap128-arm.S b/firmware/target/arm/memswap128-arm.S deleted file mode 100644 index f672def1ec..0000000000 --- a/firmware/target/arm/memswap128-arm.S +++ /dev/null @@ -1,44 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2007 by Michael Sevakis - * - * All files in this archive are subject to the GNU General Public License. - * See the file COPYING in the source tree root for full license agreement. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ - -/**************************************************************************** - * void memswap128(void *buf1, void *buf2, size_t len) - */ - .section .icode, "ax", %progbits - .align 2 - .global memswap128 - .type memswap128, %function -memswap128: - @ r0 = buf1 - @ r1 = buf2 - @ r2 = len - movs r2, r2, lsr #4 @ bytes => lines, len == 0? - moveq pc, lr @ not at least a line? leave - stmdb sp!, { r4-r10, lr } @ save registers and return address -.loop: @ - ldmia r0, { r3-r6 } @ read four longwords from buf1 - ldmia r1, { r7-r10 } @ read four longwords from buf2 - stmia r0!, { r7-r10 } @ write buf2 data to buf1, buf1 += 16 - stmia r1!, { r3-r6 } @ write buf1 data to buf2, buf2 += 16 - subs r2, r2, #1 @ len -= 1, len > 0 ? - bhi .loop @ yes? keep exchanging - ldmia sp!, { r4-r10, pc } @ restore registers and return -.end: - .size memswap128, .end-memswap128 - diff --git a/firmware/target/coldfire/memswap128-coldfire.S b/firmware/target/coldfire/memswap128-coldfire.S deleted file mode 100644 index 5de628dabd..0000000000 --- a/firmware/target/coldfire/memswap128-coldfire.S +++ /dev/null @@ -1,50 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2007 by Michael Sevakis - * - * All files in this archive are subject to the GNU General Public License. - * See the file COPYING in the source tree root for full license agreement. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ - -/**************************************************************************** - * void memswap128(void *buf1, void *buf2, size_t len) - */ - .section .icode, "ax", @progbits - .align 2 - .global memswap128 - .type memswap128, @function -memswap128: - lea.l -28(%sp), %sp | save registers - movem.l %d2-%d7/%a2, (%sp) | - movem.l 32(%sp), %a0-%a2 | %a0 = buf1 - | %a1 = buf2 - | %a2 = len - lea.l -15(%a0, %a2.l), %a2 | %a2 = end address - 15 - cmp.l %a0, %a2 | end address <= buf1? - bls.b .no_lines | not at least a line? leave -.loop: | - movem.l (%a0), %d0-%d3 | read four longwords from buf1 - movem.l (%a1), %d4-%d7 | read four longwords from buf2 - movem.l %d4-%d7, (%a0) | write buf2 data to buf1 - movem.l %d0-%d3, (%a1) | write buf1 data to buf2 - lea.l 16(%a0), %a0 | buf1 += 16 - lea.l 16(%a1), %a1 | buf2 += 16 - cmp.l %a0, %a2 | %a0 < %d0? - bhi.b .loop | yes? keep exchanging -.no_lines: | - movem.l (%sp), %d2-%d7/%a2 | restore registers - lea.l 28(%sp), %sp | - rts | -.end: - .size memswap128, .end-memswap128 diff --git a/tools/voicefont.c b/tools/voicefont.c index 94f8252e6e..6fd443a63f 100644 --- a/tools/voicefont.c +++ b/tools/voicefont.c @@ -178,7 +178,7 @@ int main (int argc, char** argv) /* Create the file format: */ /* 1st 32 bit value in the file is the version number */ - value = SWAP4(300); /* 3.00 */ + value = SWAP4(400); /* 4.00 */ fwrite(&value, sizeof(value), 1, pFile); /* 2nd 32 bit value in the file is the id number for the target