Make speex the new voice format for SWCODEC targets (non-Archos). Remove codec swapping and build speex voice decoding directly into the core binary.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15668 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/SOURCES b/apps/SOURCES
index 5e09704..597eb48 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -15,6 +15,7 @@
#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 88ecd24..f2539dc 100644
--- a/apps/codecs.c
+++ b/apps/codecs.c
@@ -67,8 +67,6 @@
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 @@
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 ecba1e9..d2ba00c 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 @@
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 e460a39..4629012 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 @@
speex_bits_reset(bits);
}
+#endif
void speex_bits_init_buffer(SpeexBits *bits, void *buff, int buf_size)
{
@@ -82,12 +84,14 @@
}
+#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 @@
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 ad1393f..70d86f6 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 <dlfcn.h> 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 2184e9c..755c5a0 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 ac8cda0..1c94b55 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 fff3bed..40dbb34 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 6af987f..1a32057 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 cd03eb5..7d29f60 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 3a910be..02a58e0 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 4cade93..6b2c698 100644
--- a/apps/dsp.c
+++ b/apps/dsp.c
@@ -162,6 +162,10 @@
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 @@
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 @@
};
/* 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 @@
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 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 @@
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 @@
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 @@
* * 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 @@
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 @@
}
}
- 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 @@
* * 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 @@
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 @@
}
#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 @@
* 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 @@
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 @@
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 @@
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 @@
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);
@@ -854,16 +842,6 @@
}
/**
- * 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.
*
* @param precut to apply in decibels (multiplied by 10)
@@ -871,8 +849,7 @@
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 @@
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 @@
}
}
+/**
+ * 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 @@
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 @@
};
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 @@
* 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 @@
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 @@
* 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 @@
/* 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
/* 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 838dc61..799d023 100644
--- a/apps/dsp.h
+++ b/apps/dsp.h
@@ -34,8 +34,14 @@
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 @@
#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 49333d1..4ed2973 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 @@
#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 @@
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_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 @@
}
}
else
+ {
return pcmbuf_request_buffer(count);
+ }
}
bool pcmbuf_is_crossfade_active(void)
@@ -1030,8 +1043,15 @@
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 @@
/* Start 1/8s into the next chunk */
pcmbuf_mix_sample = NATIVE_FREQUENCY * 4 / 16;
}
+
if (!pcmbuf_mix_chunk)
return;
@@ -1053,6 +1074,7 @@
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 bb7da52..0636245 100644
--- a/apps/pcmbuf.h
+++ b/apps/pcmbuf.h
@@ -67,16 +67,16 @@
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 90245e8..eac5307 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 @@
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 @@
#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 @@
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 @@
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 @@
/* --- 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 @@
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 @@
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 @@
}
/* --- 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 @@
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 @@
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 @@
/* 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 @@
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 @@
{
/* 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 @@
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 @@
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 @@
}
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 @@
/* 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 @@
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 @@
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 @@
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 @@
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 @@
*/
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 @@
/* 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 @@
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 @@
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 @@
#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 a80e9dd..145d30b 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -457,7 +457,7 @@
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 @@
#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 @@
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 75d8654..9123af4 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -568,7 +568,7 @@
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 @@
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 @@
#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 53729cc..332a10f 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 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 @@
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 @@
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 fcb365b..adf4155 100644
--- a/apps/talk.h
+++ b/apps/talk.h
@@ -27,7 +27,7 @@
#include <stdbool.h>
#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 0000000..8792d1c
--- /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 0000000..72c2054
--- /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 */