Add very limited resampling (it's slow as shit)

custom
jacqueline 2 years ago
parent fbebc52511
commit 3511852f39
  1. 8
      lib/libsamplerate/libsamplerate-0.2.2/include/samplerate.h
  2. 14
      lib/libsamplerate/libsamplerate-0.2.2/src/common.h
  3. 8
      lib/libsamplerate/libsamplerate-0.2.2/src/samplerate.c
  4. 6
      lib/libsamplerate/libsamplerate-0.2.2/src/src_linear.c
  5. 88
      lib/libsamplerate/libsamplerate-0.2.2/src/src_sinc.c
  6. 2
      lib/libsamplerate/libsamplerate-0.2.2/src/src_zoh.c
  7. 2
      src/audio/CMakeLists.txt
  8. 81
      src/audio/audio_task.cpp
  9. 37
      src/audio/i2s_audio_output.cpp
  10. 4
      src/audio/include/audio_sink.hpp
  11. 7
      src/audio/include/audio_task.hpp
  12. 4
      src/audio/include/i2s_audio_output.hpp
  13. 88
      src/audio/include/sink_mixer.hpp
  14. 35
      src/audio/include/stream_info.hpp
  15. 301
      src/audio/sink_mixer.cpp
  16. 2
      src/drivers/bluetooth.cpp
  17. 1
      src/drivers/i2s_dac.cpp
  18. 19
      src/drivers/include/bluetooth.hpp
  19. 6
      src/drivers/include/i2s_dac.hpp
  20. 2
      src/system_fsm/include/system_fsm.hpp
  21. 16
      src/tasks/tasks.cpp
  22. 2
      src/tasks/tasks.hpp

@ -32,7 +32,7 @@ typedef struct
int end_of_input ; int end_of_input ;
double src_ratio ; float src_ratio ;
} SRC_DATA ; } SRC_DATA ;
/* /*
@ -89,7 +89,7 @@ int src_process (SRC_STATE *state, SRC_DATA *data) ;
** Callback based processing function. Read up to frames worth of data from ** Callback based processing function. Read up to frames worth of data from
** the converter int *data and return frames read or -1 on error. ** the converter int *data and return frames read or -1 on error.
*/ */
long src_callback_read (SRC_STATE *state, double src_ratio, long frames, float *data) ; long src_callback_read (SRC_STATE *state, float src_ratio, long frames, float *data) ;
/* /*
** Simple interface for performing a single conversion from input buffer to ** Simple interface for performing a single conversion from input buffer to
@ -119,7 +119,7 @@ const char *src_get_version (void) ;
** Returns non zero on error. ** Returns non zero on error.
*/ */
int src_set_ratio (SRC_STATE *state, double new_ratio) ; int src_set_ratio (SRC_STATE *state, float new_ratio) ;
/* /*
** Get the current channel count. ** Get the current channel count.
@ -142,7 +142,7 @@ int src_reset (SRC_STATE *state) ;
** otherwise. ** otherwise.
*/ */
int src_is_valid_ratio (double ratio) ; int src_is_valid_ratio (float ratio) ;
/* /*
** Return an error number. ** Return an error number.

@ -156,7 +156,7 @@ struct SRC_STATE_tag
{ {
SRC_STATE_VT *vt ; SRC_STATE_VT *vt ;
double last_ratio, last_position ; float last_ratio, last_position ;
SRC_ERROR error ; SRC_ERROR error ;
int channels ; int channels ;
@ -209,7 +209,7 @@ static inline int
#ifdef USE_TARGET_ATTRIBUTE #ifdef USE_TARGET_ATTRIBUTE
__attribute__((target("sse2"))) __attribute__((target("sse2")))
#endif #endif
psf_lrint (double x) psf_lrint (float x)
{ {
return _mm_cvtsd_si32 (_mm_load_sd (&x)) ; return _mm_cvtsd_si32 (_mm_load_sd (&x)) ;
} }
@ -221,7 +221,7 @@ static inline int psf_lrintf (float x)
return lrintf (x) ; return lrintf (x) ;
} /* psf_lrintf */ } /* psf_lrintf */
static inline int psf_lrint (double x) static inline int psf_lrint (float x)
{ {
return lrint (x) ; return lrint (x) ;
} /* psf_lrint */ } /* psf_lrint */
@ -231,9 +231,9 @@ static inline int psf_lrint (double x)
** Common static inline functions. ** Common static inline functions.
*/ */
static inline double static inline float
fmod_one (double x) fmod_one (float x)
{ double res ; { float res ;
res = x - psf_lrint (x) ; res = x - psf_lrint (x) ;
if (res < 0.0) if (res < 0.0)
@ -243,7 +243,7 @@ fmod_one (double x)
} /* fmod_one */ } /* fmod_one */
static inline int static inline int
is_bad_src_ratio (double ratio) is_bad_src_ratio (float ratio)
{ return (ratio < (1.0 / SRC_MAX_RATIO) || ratio > (1.0 * SRC_MAX_RATIO)) ; { return (ratio < (1.0 / SRC_MAX_RATIO) || ratio > (1.0 * SRC_MAX_RATIO)) ;
} /* is_bad_src_ratio */ } /* is_bad_src_ratio */

@ -143,7 +143,7 @@ src_process (SRC_STATE *state, SRC_DATA *data)
} /* src_process */ } /* src_process */
long long
src_callback_read (SRC_STATE *state, double src_ratio, long frames, float *data) src_callback_read (SRC_STATE *state, float src_ratio, long frames, float *data)
{ {
SRC_DATA src_data ; SRC_DATA src_data ;
@ -238,7 +238,7 @@ src_callback_read (SRC_STATE *state, double src_ratio, long frames, float *data)
*/ */
int int
src_set_ratio (SRC_STATE *state, double new_ratio) src_set_ratio (SRC_STATE *state, float new_ratio)
{ {
if (state == NULL) if (state == NULL)
return SRC_ERR_BAD_STATE ; return SRC_ERR_BAD_STATE ;
@ -321,7 +321,7 @@ src_get_version (void)
} /* src_get_version */ } /* src_get_version */
int int
src_is_valid_ratio (double ratio) src_is_valid_ratio (float ratio)
{ {
if (is_bad_src_ratio (ratio)) if (is_bad_src_ratio (ratio))
return SRC_FALSE ; return SRC_FALSE ;
@ -461,7 +461,7 @@ src_int_to_float_array (const int *in, float *out, int len)
void void
src_float_to_int_array (const float *in, int *out, int len) src_float_to_int_array (const float *in, int *out, int len)
{ double scaled_value ; { float scaled_value ;
for (int i = 0 ; i < len ; i++) for (int i = 0 ; i < len ; i++)
{ scaled_value = in [i] * (8.0 * 0x10000000) ; { scaled_value = in [i] * (8.0 * 0x10000000) ;

@ -53,7 +53,7 @@ static SRC_STATE_VT linear_state_vt =
static SRC_ERROR static SRC_ERROR
linear_vari_process (SRC_STATE *state, SRC_DATA *data) linear_vari_process (SRC_STATE *state, SRC_DATA *data)
{ LINEAR_DATA *priv ; { LINEAR_DATA *priv ;
double src_ratio, input_index, rem ; float src_ratio, input_index, rem ;
int ch ; int ch ;
if (data->input_frames <= 0) if (data->input_frames <= 0)
@ -93,7 +93,7 @@ linear_vari_process (SRC_STATE *state, SRC_DATA *data)
for (ch = 0 ; ch < state->channels ; ch++) for (ch = 0 ; ch < state->channels ; ch++)
{ data->data_out [priv->out_gen] = (float) (priv->last_value [ch] + input_index * { data->data_out [priv->out_gen] = (float) (priv->last_value [ch] + input_index *
((double) data->data_in [ch] - priv->last_value [ch])) ; ((float) data->data_in [ch] - priv->last_value [ch])) ;
priv->out_gen ++ ; priv->out_gen ++ ;
} ; } ;
@ -120,7 +120,7 @@ linear_vari_process (SRC_STATE *state, SRC_DATA *data)
for (ch = 0 ; ch < state->channels ; ch++) for (ch = 0 ; ch < state->channels ; ch++)
{ data->data_out [priv->out_gen] = (float) (data->data_in [priv->in_used - state->channels + ch] + input_index * { data->data_out [priv->out_gen] = (float) (data->data_in [priv->in_used - state->channels + ch] + input_index *
((double) data->data_in [priv->in_used + ch] - data->data_in [priv->in_used - state->channels + ch])) ; ((float) data->data_in [priv->in_used + ch] - data->data_in [priv->in_used - state->channels + ch])) ;
priv->out_gen ++ ; priv->out_gen ++ ;
} ; } ;

@ -26,7 +26,7 @@
#define MAKE_INCREMENT_T(x) ((increment_t) (x)) #define MAKE_INCREMENT_T(x) ((increment_t) (x))
#define SHIFT_BITS 12 #define SHIFT_BITS 12
#define FP_ONE ((double) (((increment_t) 1) << SHIFT_BITS)) #define FP_ONE ((float) (((increment_t) 1) << SHIFT_BITS))
#define INV_FP_ONE (1.0 / FP_ONE) #define INV_FP_ONE (1.0 / FP_ONE)
/* Customixe max channls from Kconfig. */ /* Customixe max channls from Kconfig. */
@ -61,14 +61,14 @@ typedef struct
int coeff_half_len, index_inc ; int coeff_half_len, index_inc ;
double src_ratio, input_index ; float src_ratio, input_index ;
coeff_t const *coeffs ; coeff_t const *coeffs ;
int b_current, b_end, b_real_end, b_len ; int b_current, b_end, b_real_end, b_len ;
/* Sure hope noone does more than 128 channels at once. */ /* Sure hope noone does more than 128 channels at once. */
double left_calc [MAX_CHANNELS], right_calc [MAX_CHANNELS] ; float left_calc [MAX_CHANNELS], right_calc [MAX_CHANNELS] ;
float *buffer ; float *buffer ;
} SINC_FILTER ; } SINC_FILTER ;
@ -131,9 +131,9 @@ static SRC_STATE_VT sinc_mono_state_vt =
} ; } ;
static inline increment_t static inline increment_t
double_to_fp (double x) float_to_fp (float x)
{ return (increment_t) (psf_lrint ((x) * FP_ONE)) ; { return (increment_t) (psf_lrint ((x) * FP_ONE)) ;
} /* double_to_fp */ } /* float_to_fp */
static inline increment_t static inline increment_t
int_to_fp (int x) int_to_fp (int x)
@ -150,10 +150,10 @@ fp_fraction_part (increment_t x)
{ return ((x) & ((((increment_t) 1) << SHIFT_BITS) - 1)) ; { return ((x) & ((((increment_t) 1) << SHIFT_BITS) - 1)) ;
} /* fp_fraction_part */ } /* fp_fraction_part */
static inline double static inline float
fp_to_double (increment_t x) fp_to_float (increment_t x)
{ return fp_fraction_part (x) * INV_FP_ONE ; { return fp_fraction_part (x) * INV_FP_ONE ;
} /* fp_to_double */ } /* fp_to_float */
static inline int static inline int
int_div_ceil (int divident, int divisor) /* == (int) ceil ((float) divident / divisor) */ int_div_ceil (int divident, int divisor) /* == (int) ceil ((float) divident / divisor) */
@ -367,9 +367,9 @@ sinc_copy (SRC_STATE *state)
** Beware all ye who dare pass this point. There be dragons here. ** Beware all ye who dare pass this point. There be dragons here.
*/ */
static inline double static inline float
calc_output_single (SINC_FILTER *filter, increment_t increment, increment_t start_filter_index) calc_output_single (SINC_FILTER *filter, increment_t increment, increment_t start_filter_index)
{ double fraction, left, right, icoeff ; { float fraction, left, right, icoeff ;
increment_t filter_index, max_filter_index ; increment_t filter_index, max_filter_index ;
int data_index, coeff_count, indx ; int data_index, coeff_count, indx ;
@ -391,7 +391,7 @@ calc_output_single (SINC_FILTER *filter, increment_t increment, increment_t star
} }
left = 0.0 ; left = 0.0 ;
while (filter_index >= MAKE_INCREMENT_T (0)) while (filter_index >= MAKE_INCREMENT_T (0))
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -411,7 +411,7 @@ calc_output_single (SINC_FILTER *filter, increment_t increment, increment_t star
right = 0.0 ; right = 0.0 ;
do do
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx < filter->coeff_half_len + 2) ; assert (indx < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -430,7 +430,7 @@ calc_output_single (SINC_FILTER *filter, increment_t increment, increment_t star
static SRC_ERROR static SRC_ERROR
sinc_mono_vari_process (SRC_STATE *state, SRC_DATA *data) sinc_mono_vari_process (SRC_STATE *state, SRC_DATA *data)
{ SINC_FILTER *filter ; { SINC_FILTER *filter ;
double input_index, src_ratio, count, float_increment, terminate, rem ; float input_index, src_ratio, count, float_increment, terminate, rem ;
increment_t increment, start_filter_index ; increment_t increment, start_filter_index ;
int half_filter_chan_len, samples_in_hand ; int half_filter_chan_len, samples_in_hand ;
@ -493,9 +493,9 @@ sinc_mono_vari_process (SRC_STATE *state, SRC_DATA *data)
src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ; src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ;
float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ; float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ;
increment = double_to_fp (float_increment) ; increment = float_to_fp (float_increment) ;
start_filter_index = double_to_fp (input_index * float_increment) ; start_filter_index = float_to_fp (input_index * float_increment) ;
data->data_out [filter->out_gen] = (float) ((float_increment / filter->index_inc) * data->data_out [filter->out_gen] = (float) ((float_increment / filter->index_inc) *
calc_output_single (filter, increment, start_filter_index)) ; calc_output_single (filter, increment, start_filter_index)) ;
@ -521,8 +521,8 @@ sinc_mono_vari_process (SRC_STATE *state, SRC_DATA *data)
} /* sinc_mono_vari_process */ } /* sinc_mono_vari_process */
static inline void static inline void
calc_output_stereo (SINC_FILTER *filter, int channels, increment_t increment, increment_t start_filter_index, double scale, float * output) calc_output_stereo (SINC_FILTER *filter, int channels, increment_t increment, increment_t start_filter_index, float scale, float * output)
{ double fraction, left [2], right [2], icoeff ; { float fraction, left [2], right [2], icoeff ;
increment_t filter_index, max_filter_index ; increment_t filter_index, max_filter_index ;
int data_index, coeff_count, indx ; int data_index, coeff_count, indx ;
@ -544,7 +544,7 @@ calc_output_stereo (SINC_FILTER *filter, int channels, increment_t increment, in
} }
left [0] = left [1] = 0.0 ; left [0] = left [1] = 0.0 ;
while (filter_index >= MAKE_INCREMENT_T (0)) while (filter_index >= MAKE_INCREMENT_T (0))
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -565,7 +565,7 @@ calc_output_stereo (SINC_FILTER *filter, int channels, increment_t increment, in
right [0] = right [1] = 0.0 ; right [0] = right [1] = 0.0 ;
do do
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -586,7 +586,7 @@ calc_output_stereo (SINC_FILTER *filter, int channels, increment_t increment, in
SRC_ERROR SRC_ERROR
sinc_stereo_vari_process (SRC_STATE *state, SRC_DATA *data) sinc_stereo_vari_process (SRC_STATE *state, SRC_DATA *data)
{ SINC_FILTER *filter ; { SINC_FILTER *filter ;
double input_index, src_ratio, count, float_increment, terminate, rem ; float input_index, src_ratio, count, float_increment, terminate, rem ;
increment_t increment, start_filter_index ; increment_t increment, start_filter_index ;
int half_filter_chan_len, samples_in_hand ; int half_filter_chan_len, samples_in_hand ;
@ -649,9 +649,9 @@ sinc_stereo_vari_process (SRC_STATE *state, SRC_DATA *data)
src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ; src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ;
float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ; float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ;
increment = double_to_fp (float_increment) ; increment = float_to_fp (float_increment) ;
start_filter_index = double_to_fp (input_index * float_increment) ; start_filter_index = float_to_fp (input_index * float_increment) ;
calc_output_stereo (filter, state->channels, increment, start_filter_index, float_increment / filter->index_inc, data->data_out + filter->out_gen) ; calc_output_stereo (filter, state->channels, increment, start_filter_index, float_increment / filter->index_inc, data->data_out + filter->out_gen) ;
filter->out_gen += 2 ; filter->out_gen += 2 ;
@ -676,8 +676,8 @@ sinc_stereo_vari_process (SRC_STATE *state, SRC_DATA *data)
} /* sinc_stereo_vari_process */ } /* sinc_stereo_vari_process */
static inline void static inline void
calc_output_quad (SINC_FILTER *filter, int channels, increment_t increment, increment_t start_filter_index, double scale, float * output) calc_output_quad (SINC_FILTER *filter, int channels, increment_t increment, increment_t start_filter_index, float scale, float * output)
{ double fraction, left [4], right [4], icoeff ; { float fraction, left [4], right [4], icoeff ;
increment_t filter_index, max_filter_index ; increment_t filter_index, max_filter_index ;
int data_index, coeff_count, indx ; int data_index, coeff_count, indx ;
@ -699,7 +699,7 @@ calc_output_quad (SINC_FILTER *filter, int channels, increment_t increment, incr
} }
left [0] = left [1] = left [2] = left [3] = 0.0 ; left [0] = left [1] = left [2] = left [3] = 0.0 ;
while (filter_index >= MAKE_INCREMENT_T (0)) while (filter_index >= MAKE_INCREMENT_T (0))
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -720,7 +720,7 @@ calc_output_quad (SINC_FILTER *filter, int channels, increment_t increment, incr
right [0] = right [1] = right [2] = right [3] = 0.0 ; right [0] = right [1] = right [2] = right [3] = 0.0 ;
do do
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -742,7 +742,7 @@ calc_output_quad (SINC_FILTER *filter, int channels, increment_t increment, incr
SRC_ERROR SRC_ERROR
sinc_quad_vari_process (SRC_STATE *state, SRC_DATA *data) sinc_quad_vari_process (SRC_STATE *state, SRC_DATA *data)
{ SINC_FILTER *filter ; { SINC_FILTER *filter ;
double input_index, src_ratio, count, float_increment, terminate, rem ; float input_index, src_ratio, count, float_increment, terminate, rem ;
increment_t increment, start_filter_index ; increment_t increment, start_filter_index ;
int half_filter_chan_len, samples_in_hand ; int half_filter_chan_len, samples_in_hand ;
@ -805,9 +805,9 @@ sinc_quad_vari_process (SRC_STATE *state, SRC_DATA *data)
src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ; src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ;
float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ; float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ;
increment = double_to_fp (float_increment) ; increment = float_to_fp (float_increment) ;
start_filter_index = double_to_fp (input_index * float_increment) ; start_filter_index = float_to_fp (input_index * float_increment) ;
calc_output_quad (filter, state->channels, increment, start_filter_index, float_increment / filter->index_inc, data->data_out + filter->out_gen) ; calc_output_quad (filter, state->channels, increment, start_filter_index, float_increment / filter->index_inc, data->data_out + filter->out_gen) ;
filter->out_gen += 4 ; filter->out_gen += 4 ;
@ -832,8 +832,8 @@ sinc_quad_vari_process (SRC_STATE *state, SRC_DATA *data)
} /* sinc_quad_vari_process */ } /* sinc_quad_vari_process */
static inline void static inline void
calc_output_hex (SINC_FILTER *filter, int channels, increment_t increment, increment_t start_filter_index, double scale, float * output) calc_output_hex (SINC_FILTER *filter, int channels, increment_t increment, increment_t start_filter_index, float scale, float * output)
{ double fraction, left [6], right [6], icoeff ; { float fraction, left [6], right [6], icoeff ;
increment_t filter_index, max_filter_index ; increment_t filter_index, max_filter_index ;
int data_index, coeff_count, indx ; int data_index, coeff_count, indx ;
@ -855,7 +855,7 @@ calc_output_hex (SINC_FILTER *filter, int channels, increment_t increment, incre
} }
left [0] = left [1] = left [2] = left [3] = left [4] = left [5] = 0.0 ; left [0] = left [1] = left [2] = left [3] = left [4] = left [5] = 0.0 ;
while (filter_index >= MAKE_INCREMENT_T (0)) while (filter_index >= MAKE_INCREMENT_T (0))
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -876,7 +876,7 @@ calc_output_hex (SINC_FILTER *filter, int channels, increment_t increment, incre
right [0] = right [1] = right [2] = right [3] = right [4] = right [5] = 0.0 ; right [0] = right [1] = right [2] = right [3] = right [4] = right [5] = 0.0 ;
do do
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -897,7 +897,7 @@ calc_output_hex (SINC_FILTER *filter, int channels, increment_t increment, incre
SRC_ERROR SRC_ERROR
sinc_hex_vari_process (SRC_STATE *state, SRC_DATA *data) sinc_hex_vari_process (SRC_STATE *state, SRC_DATA *data)
{ SINC_FILTER *filter ; { SINC_FILTER *filter ;
double input_index, src_ratio, count, float_increment, terminate, rem ; float input_index, src_ratio, count, float_increment, terminate, rem ;
increment_t increment, start_filter_index ; increment_t increment, start_filter_index ;
int half_filter_chan_len, samples_in_hand ; int half_filter_chan_len, samples_in_hand ;
@ -960,9 +960,9 @@ sinc_hex_vari_process (SRC_STATE *state, SRC_DATA *data)
src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ; src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ;
float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ; float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ;
increment = double_to_fp (float_increment) ; increment = float_to_fp (float_increment) ;
start_filter_index = double_to_fp (input_index * float_increment) ; start_filter_index = float_to_fp (input_index * float_increment) ;
calc_output_hex (filter, state->channels, increment, start_filter_index, float_increment / filter->index_inc, data->data_out + filter->out_gen) ; calc_output_hex (filter, state->channels, increment, start_filter_index, float_increment / filter->index_inc, data->data_out + filter->out_gen) ;
filter->out_gen += 6 ; filter->out_gen += 6 ;
@ -987,10 +987,10 @@ sinc_hex_vari_process (SRC_STATE *state, SRC_DATA *data)
} /* sinc_hex_vari_process */ } /* sinc_hex_vari_process */
static inline void static inline void
calc_output_multi (SINC_FILTER *filter, increment_t increment, increment_t start_filter_index, int channels, double scale, float * output) calc_output_multi (SINC_FILTER *filter, increment_t increment, increment_t start_filter_index, int channels, float scale, float * output)
{ double fraction, icoeff ; { float fraction, icoeff ;
/* The following line is 1999 ISO Standard C. If your compiler complains, get a better compiler. */ /* The following line is 1999 ISO Standard C. If your compiler complains, get a better compiler. */
double *left, *right ; float *left, *right ;
increment_t filter_index, max_filter_index ; increment_t filter_index, max_filter_index ;
int data_index, coeff_count, indx ; int data_index, coeff_count, indx ;
@ -1017,7 +1017,7 @@ calc_output_multi (SINC_FILTER *filter, increment_t increment, increment_t start
memset (left, 0, sizeof (left [0]) * channels) ; memset (left, 0, sizeof (left [0]) * channels) ;
while (filter_index >= MAKE_INCREMENT_T (0)) while (filter_index >= MAKE_INCREMENT_T (0))
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -1039,7 +1039,7 @@ calc_output_multi (SINC_FILTER *filter, increment_t increment, increment_t start
memset (right, 0, sizeof (right [0]) * channels) ; memset (right, 0, sizeof (right [0]) * channels) ;
do do
{ fraction = fp_to_double (filter_index) ; { fraction = fp_to_float (filter_index) ;
indx = fp_to_int (filter_index) ; indx = fp_to_int (filter_index) ;
assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ; assert (indx >= 0 && indx + 1 < filter->coeff_half_len + 2) ;
icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ; icoeff = filter->coeffs [indx] + fraction * (filter->coeffs [indx + 1] - filter->coeffs [indx]) ;
@ -1062,7 +1062,7 @@ calc_output_multi (SINC_FILTER *filter, increment_t increment, increment_t start
static SRC_ERROR static SRC_ERROR
sinc_multichan_vari_process (SRC_STATE *state, SRC_DATA *data) sinc_multichan_vari_process (SRC_STATE *state, SRC_DATA *data)
{ SINC_FILTER *filter ; { SINC_FILTER *filter ;
double input_index, src_ratio, count, float_increment, terminate, rem ; float input_index, src_ratio, count, float_increment, terminate, rem ;
increment_t increment, start_filter_index ; increment_t increment, start_filter_index ;
int half_filter_chan_len, samples_in_hand ; int half_filter_chan_len, samples_in_hand ;
@ -1125,9 +1125,9 @@ sinc_multichan_vari_process (SRC_STATE *state, SRC_DATA *data)
src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ; src_ratio = state->last_ratio + filter->out_gen * (data->src_ratio - state->last_ratio) / filter->out_count ;
float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ; float_increment = filter->index_inc * (src_ratio < 1.0 ? src_ratio : 1.0) ;
increment = double_to_fp (float_increment) ; increment = float_to_fp (float_increment) ;
start_filter_index = double_to_fp (input_index * float_increment) ; start_filter_index = float_to_fp (input_index * float_increment) ;
calc_output_multi (filter, increment, start_filter_index, state->channels, float_increment / filter->index_inc, data->data_out + filter->out_gen) ; calc_output_multi (filter, increment, start_filter_index, state->channels, float_increment / filter->index_inc, data->data_out + filter->out_gen) ;
filter->out_gen += state->channels ; filter->out_gen += state->channels ;

@ -51,7 +51,7 @@ static SRC_STATE_VT zoh_state_vt =
static SRC_ERROR static SRC_ERROR
zoh_vari_process (SRC_STATE *state, SRC_DATA *data) zoh_vari_process (SRC_STATE *state, SRC_DATA *data)
{ ZOH_DATA *priv ; { ZOH_DATA *priv ;
double src_ratio, input_index, rem ; float src_ratio, input_index, rem ;
int ch ; int ch ;
if (data->input_frames <= 0) if (data->input_frames <= 0)

@ -5,7 +5,7 @@
idf_component_register( idf_component_register(
SRCS "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" SRCS "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp"
"stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.cpp" "track_queue.cpp" "stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.cpp" "track_queue.cpp"
"stream_event.cpp" "stream_info.cpp" "audio_fsm.cpp" "stream_event.cpp" "stream_info.cpp" "audio_fsm.cpp" "sink_mixer.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" "database" "system_fsm" "playlist" "libsamplerate") REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" "database" "system_fsm" "playlist" "libsamplerate")

@ -34,6 +34,7 @@
#include "freertos/queue.h" #include "freertos/queue.h"
#include "freertos/ringbuf.h" #include "freertos/ringbuf.h"
#include "pipeline.hpp" #include "pipeline.hpp"
#include "sink_mixer.hpp"
#include "span.hpp" #include "span.hpp"
#include "arena.hpp" #include "arena.hpp"
@ -115,14 +116,12 @@ AudioTask::AudioTask(IAudioSource* source, IAudioSink* sink)
: source_(source), : source_(source),
sink_(sink), sink_(sink),
codec_(), codec_(),
mixer_(new SinkMixer(sink->stream())),
timer_(), timer_(),
has_begun_decoding_(false), has_begun_decoding_(false),
current_input_format_(), current_input_format_(),
current_output_format_(), current_output_format_(),
sample_buffer_(reinterpret_cast<std::byte*>( codec_buffer_(new RawStream(kSampleBufferSize)) {}
heap_caps_malloc(kSampleBufferSize,
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT))),
sample_buffer_len_(kSampleBufferSize) {}
void AudioTask::Main() { void AudioTask::Main() {
for (;;) { for (;;) {
@ -246,13 +245,17 @@ auto AudioTask::BeginDecoding(InputStream& stream) -> bool {
return false; return false;
} }
OutputStream writer{codec_buffer_.get()};
writer.prepare(new_format, {});
return true; return true;
} }
auto AudioTask::ContinueDecoding(InputStream& stream) -> bool { auto AudioTask::ContinueDecoding(InputStream& stream) -> bool {
while (!stream.data().empty()) { while (!stream.data().empty()) {
auto res = codec_->ContinueStream(stream.data(), OutputStream writer{codec_buffer_.get()};
{sample_buffer_, sample_buffer_len_});
auto res = codec_->ContinueStream(stream.data(), writer.data());
stream.consume(res.first); stream.consume(res.first);
@ -263,9 +266,10 @@ auto AudioTask::ContinueDecoding(InputStream& stream) -> bool {
return false; return false;
} }
} else { } else {
xStreamBufferSend(sink_->stream(), sample_buffer_, writer.add(res.second->bytes_written);
res.second->bytes_written, portMAX_DELAY);
timer_->AddBytes(res.second->bytes_written); InputStream reader{codec_buffer_.get()};
SendToSink(reader);
} }
} }
return true; return true;
@ -284,21 +288,22 @@ auto AudioTask::FinishDecoding(InputStream& stream) -> void {
std::unique_ptr<RawStream> mad_buffer; std::unique_ptr<RawStream> mad_buffer;
mad_buffer.reset(new RawStream(stream.data().size_bytes() + 8)); mad_buffer.reset(new RawStream(stream.data().size_bytes() + 8));
OutputStream writer{mad_buffer.get()}; OutputStream mad_writer{mad_buffer.get()};
std::copy(stream.data().begin(), stream.data().end(), std::copy(stream.data().begin(), stream.data().end(),
writer.data().begin()); mad_writer.data().begin());
std::fill(writer.data().begin(), writer.data().end(), std::byte{0}); std::fill(mad_writer.data().begin(), mad_writer.data().end(), std::byte{0});
InputStream padded_stream{mad_buffer.get()}; InputStream padded_stream{mad_buffer.get()};
auto res = codec_->ContinueStream(stream.data(), OutputStream writer{codec_buffer_.get()};
{sample_buffer_, sample_buffer_len_}); auto res = codec_->ContinueStream(stream.data(), writer.data());
if (res.second.has_error()) { if (res.second.has_error()) {
return; return;
} }
xStreamBufferSend(sink_->stream(), sample_buffer_, writer.add(res.second->bytes_written);
res.second->bytes_written, portMAX_DELAY);
timer_->AddBytes(res.second->bytes_written); InputStream reader{codec_buffer_.get()};
SendToSink(reader);
} }
} }
@ -319,24 +324,31 @@ auto AudioTask::ForwardPcmStream(StreamInfo::Pcm& format,
xStreamBufferSend(sink_->stream(), samples.data(), samples.size_bytes(), xStreamBufferSend(sink_->stream(), samples.data(), samples.size_bytes(),
portMAX_DELAY); portMAX_DELAY);
timer_->AddBytes(samples.size_bytes()); timer_->AddBytes(samples.size_bytes());
InputStream reader{codec_buffer_.get()};
SendToSink(reader);
return true; return true;
} }
auto AudioTask::ConfigureSink(const StreamInfo::Pcm& format, auto AudioTask::ConfigureSink(const StreamInfo::Pcm& format,
const Duration& duration) -> bool { const Duration& duration) -> bool {
if (format != current_output_format_) { if (format != current_output_format_) {
// The new format is different to the old one. Wait for the sink to drain current_output_format_ = format;
// before continuing. StreamInfo::Pcm new_sink_format = sink_->PrepareFormat(format);
while (!xStreamBufferIsEmpty(sink_->stream())) { if (new_sink_format != current_sink_format_) {
ESP_LOGI(kTag, "waiting for sink stream to drain..."); current_sink_format_ = new_sink_format;
// TODO(jacqueline): Get the sink drain ISR to notify us of this
// via semaphore instead of busy-ish waiting. // The new format is different to the old one. Wait for the sink to drain
vTaskDelay(pdMS_TO_TICKS(100)); // before continuing.
} while (!xStreamBufferIsEmpty(sink_->stream())) {
ESP_LOGI(kTag, "waiting for sink stream to drain...");
// TODO(jacqueline): Get the sink drain ISR to notify us of this
// via semaphore instead of busy-ish waiting.
vTaskDelay(pdMS_TO_TICKS(10));
}
ESP_LOGI(kTag, "configuring sink"); ESP_LOGI(kTag, "configuring sink");
if (!sink_->Configure(format)) { sink_->Configure(new_sink_format);
return false;
} }
} }
@ -345,4 +357,17 @@ auto AudioTask::ConfigureSink(const StreamInfo::Pcm& format,
return true; return true;
} }
auto AudioTask::SendToSink(InputStream& stream) -> void {
std::size_t bytes_to_send = stream.data().size_bytes();
std::size_t bytes_sent;
if (stream.info().format_as<StreamInfo::Pcm>() == current_sink_format_) {
bytes_sent = xStreamBufferSend(sink_->stream(), stream.data().data(),
bytes_to_send, portMAX_DELAY);
stream.consume(bytes_sent);
} else {
bytes_sent = mixer_->MixAndSend(stream, current_sink_format_.value());
}
timer_->AddBytes(bytes_sent);
}
} // namespace audio } // namespace audio

@ -115,10 +115,19 @@ auto I2SAudioOutput::AdjustVolumeDown() -> bool {
return true; return true;
} }
auto I2SAudioOutput::Configure(const StreamInfo::Pcm& pcm) -> bool { auto I2SAudioOutput::PrepareFormat(const StreamInfo::Pcm& orig)
-> StreamInfo::Pcm {
return StreamInfo::Pcm{
.channels = std::min<uint8_t>(orig.channels, 2),
.bits_per_sample = std::clamp<uint8_t>(orig.bits_per_sample, 16, 32),
.sample_rate = std::clamp<uint32_t>(orig.sample_rate, 8000, 96000),
};
}
auto I2SAudioOutput::Configure(const StreamInfo::Pcm& pcm) -> void {
if (current_config_ && pcm == *current_config_) { if (current_config_ && pcm == *current_config_) {
ESP_LOGI(kTag, "ignoring unchanged format"); ESP_LOGI(kTag, "ignoring unchanged format");
return true; return;
} }
ESP_LOGI(kTag, "incoming audio stream: %u ch %u bpp @ %lu Hz", pcm.channels, ESP_LOGI(kTag, "incoming audio stream: %u ch %u bpp @ %lu Hz", pcm.channels,
@ -134,7 +143,7 @@ auto I2SAudioOutput::Configure(const StreamInfo::Pcm& pcm) -> bool {
break; break;
default: default:
ESP_LOGE(kTag, "dropping stream with out of bounds channels"); ESP_LOGE(kTag, "dropping stream with out of bounds channels");
return false; return;
} }
drivers::I2SDac::BitsPerSample bps; drivers::I2SDac::BitsPerSample bps;
@ -150,30 +159,36 @@ auto I2SAudioOutput::Configure(const StreamInfo::Pcm& pcm) -> bool {
break; break;
default: default:
ESP_LOGE(kTag, "dropping stream with unknown bps"); ESP_LOGE(kTag, "dropping stream with unknown bps");
return false; return;
} }
drivers::I2SDac::SampleRate sample_rate; drivers::I2SDac::SampleRate sample_rate;
switch (pcm.sample_rate) { switch (pcm.sample_rate) {
case 8000:
sample_rate = drivers::I2SDac::SAMPLE_RATE_8;
break;
case 32000:
sample_rate = drivers::I2SDac::SAMPLE_RATE_32;
break;
case 44100: case 44100:
sample_rate = drivers::I2SDac::SAMPLE_RATE_44_1; sample_rate = drivers::I2SDac::SAMPLE_RATE_44_1;
break; break;
case 48000: case 48000:
sample_rate = drivers::I2SDac::SAMPLE_RATE_48; sample_rate = drivers::I2SDac::SAMPLE_RATE_48;
break; break;
case 88200:
sample_rate = drivers::I2SDac::SAMPLE_RATE_88_2;
break;
case 96000:
sample_rate = drivers::I2SDac::SAMPLE_RATE_96;
break;
default: default:
ESP_LOGE(kTag, "dropping stream with unknown rate"); ESP_LOGE(kTag, "dropping stream with unknown rate");
return false; return;
} }
dac_->Reconfigure(ch, bps, sample_rate); dac_->Reconfigure(ch, bps, sample_rate);
current_config_ = pcm; current_config_ = pcm;
return true;
}
auto I2SAudioOutput::Send(const cpp::span<std::byte>& data) -> void {
dac_->WriteData(data);
} }
} // namespace audio } // namespace audio

@ -38,8 +38,8 @@ class IAudioSink {
virtual auto AdjustVolumeUp() -> bool = 0; virtual auto AdjustVolumeUp() -> bool = 0;
virtual auto AdjustVolumeDown() -> bool = 0; virtual auto AdjustVolumeDown() -> bool = 0;
virtual auto Configure(const StreamInfo::Pcm& format) -> bool = 0; virtual auto PrepareFormat(const StreamInfo::Pcm&) -> StreamInfo::Pcm = 0;
virtual auto Send(const cpp::span<std::byte>& data) -> void = 0; virtual auto Configure(const StreamInfo::Pcm& format) -> void = 0;
auto stream() -> StreamBufferHandle_t { return stream_; } auto stream() -> StreamBufferHandle_t { return stream_; }
}; };

@ -14,6 +14,7 @@
#include "audio_source.hpp" #include "audio_source.hpp"
#include "codec.hpp" #include "codec.hpp"
#include "pipeline.hpp" #include "pipeline.hpp"
#include "sink_mixer.hpp"
#include "stream_info.hpp" #include "stream_info.hpp"
namespace audio { namespace audio {
@ -63,18 +64,20 @@ class AudioTask {
auto ForwardPcmStream(StreamInfo::Pcm&, cpp::span<const std::byte>) -> bool; auto ForwardPcmStream(StreamInfo::Pcm&, cpp::span<const std::byte>) -> bool;
auto ConfigureSink(const StreamInfo::Pcm&, const Duration&) -> bool; auto ConfigureSink(const StreamInfo::Pcm&, const Duration&) -> bool;
auto SendToSink(InputStream&) -> void;
IAudioSource* source_; IAudioSource* source_;
IAudioSink* sink_; IAudioSink* sink_;
std::unique_ptr<codecs::ICodec> codec_; std::unique_ptr<codecs::ICodec> codec_;
std::unique_ptr<SinkMixer> mixer_;
std::unique_ptr<Timer> timer_; std::unique_ptr<Timer> timer_;
bool has_begun_decoding_; bool has_begun_decoding_;
std::optional<StreamInfo::Format> current_input_format_; std::optional<StreamInfo::Format> current_input_format_;
std::optional<StreamInfo::Pcm> current_output_format_; std::optional<StreamInfo::Pcm> current_output_format_;
std::optional<StreamInfo::Pcm> current_sink_format_;
std::byte* sample_buffer_; std::unique_ptr<RawStream> codec_buffer_;
std::size_t sample_buffer_len_;
}; };
} // namespace audio } // namespace audio

@ -35,8 +35,8 @@ class I2SAudioOutput : public IAudioSink {
auto AdjustVolumeUp() -> bool override; auto AdjustVolumeUp() -> bool override;
auto AdjustVolumeDown() -> bool override; auto AdjustVolumeDown() -> bool override;
auto Configure(const StreamInfo::Pcm& format) -> bool override; auto PrepareFormat(const StreamInfo::Pcm&) -> StreamInfo::Pcm override;
auto Send(const cpp::span<std::byte>& data) -> void override; auto Configure(const StreamInfo::Pcm& format) -> void override;
I2SAudioOutput(const I2SAudioOutput&) = delete; I2SAudioOutput(const I2SAudioOutput&) = delete;
I2SAudioOutput& operator=(const I2SAudioOutput&) = delete; I2SAudioOutput& operator=(const I2SAudioOutput&) = delete;

@ -0,0 +1,88 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include <sys/_stdint.h>
#include <cstdint>
#include <memory>
#include "samplerate.h"
#include "audio_decoder.hpp"
#include "audio_sink.hpp"
#include "audio_source.hpp"
#include "codec.hpp"
#include "pipeline.hpp"
#include "stream_info.hpp"
namespace audio {
/*
* Handles the final downmix + resample + quantisation stage of audio,
* generation sending the result directly to an IAudioSink.
*/
class SinkMixer {
public:
SinkMixer(StreamBufferHandle_t dest);
~SinkMixer();
auto MixAndSend(InputStream&, const StreamInfo::Pcm&) -> std::size_t;
private:
auto Main() -> void;
auto SetTargetFormat(const StreamInfo::Pcm& format) -> void;
auto HandleBytes() -> void;
template <typename T>
auto ConvertFixedToFloating(InputStream&, OutputStream&) -> void;
auto Resample(float, int, InputStream&, OutputStream&) -> void;
template <typename T>
auto Quantise(InputStream&) -> std::size_t;
enum class Command {
kReadBytes,
kSetSourceFormat,
kSetTargetFormat,
};
struct Args {
Command cmd;
StreamInfo::Pcm format;
};
QueueHandle_t commands_;
SemaphoreHandle_t is_idle_;
SRC_STATE* resampler_;
std::unique_ptr<RawStream> input_stream_;
std::unique_ptr<RawStream> floating_point_stream_;
std::unique_ptr<RawStream> resampled_stream_;
cpp::span<std::byte> quantisation_buffer_;
cpp::span<short> quantisation_buffer_as_shorts_;
cpp::span<int> quantisation_buffer_as_ints_;
StreamInfo::Pcm target_format_;
StreamBufferHandle_t source_;
StreamBufferHandle_t sink_;
};
template <>
auto SinkMixer::ConvertFixedToFloating<short>(InputStream&, OutputStream&)
-> void;
template <>
auto SinkMixer::ConvertFixedToFloating<int>(InputStream&, OutputStream&)
-> void;
template <>
auto SinkMixer::Quantise<short>(InputStream&) -> std::size_t;
template <>
auto SinkMixer::Quantise<int>(InputStream&) -> std::size_t;
} // namespace audio

@ -56,6 +56,18 @@ class StreamInfo {
bool operator==(const Encoded&) const = default; bool operator==(const Encoded&) const = default;
}; };
/*
* Two-channel, interleaved, 32-bit floating point pcm samples.
*/
struct FloatingPointPcm {
// Number of channels in this stream.
uint8_t channels;
// The sample rate.
uint32_t sample_rate;
bool operator==(const FloatingPointPcm&) const = default;
};
struct Pcm { struct Pcm {
// Number of channels in this stream. // Number of channels in this stream.
uint8_t channels; uint8_t channels;
@ -64,10 +76,14 @@ class StreamInfo {
// The sample rate. // The sample rate.
uint32_t sample_rate; uint32_t sample_rate;
auto real_bytes_per_sample() const -> uint8_t {
return bits_per_sample == 16 ? 2 : 4;
}
bool operator==(const Pcm&) const = default; bool operator==(const Pcm&) const = default;
}; };
typedef std::variant<std::monostate, Encoded, Pcm> Format; typedef std::variant<std::monostate, Encoded, FloatingPointPcm, Pcm> Format;
auto format() const -> const Format& { return format_; } auto format() const -> const Format& { return format_; }
auto set_format(Format f) -> void { format_ = f; } auto set_format(Format f) -> void { format_ = f; }
@ -98,6 +114,12 @@ class RawStream {
auto info() -> StreamInfo& { return info_; } auto info() -> StreamInfo& { return info_; }
auto data() -> cpp::span<std::byte>; auto data() -> cpp::span<std::byte>;
template <typename T>
auto data_as() -> cpp::span<T> {
auto orig = data();
return {reinterpret_cast<T*>(orig.data()), orig.size_bytes() / sizeof(T)};
}
auto empty() const -> bool { return info_.bytes_in_stream() == 0; }
private: private:
StreamInfo info_; StreamInfo info_;
@ -114,6 +136,12 @@ class InputStream {
const StreamInfo& info() const; const StreamInfo& info() const;
cpp::span<const std::byte> data() const; cpp::span<const std::byte> data() const;
template <typename T>
auto data_as() const -> cpp::span<const T> {
auto orig = data();
return {reinterpret_cast<const T*>(orig.data()),
orig.size_bytes() / sizeof(T)};
}
private: private:
RawStream* raw_; RawStream* raw_;
@ -131,6 +159,11 @@ class OutputStream {
const StreamInfo& info() const; const StreamInfo& info() const;
cpp::span<std::byte> data() const; cpp::span<std::byte> data() const;
template <typename T>
auto data_as() const -> cpp::span<T> {
auto orig = data();
return {reinterpret_cast<T*>(orig.data()), orig.size_bytes() / sizeof(T)};
}
private: private:
RawStream* raw_; RawStream* raw_;

@ -0,0 +1,301 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "sink_mixer.hpp"
#include <stdint.h>
#include <cmath>
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
#include "samplerate.h"
#include "stream_info.hpp"
#include "tasks.hpp"
static constexpr char kTag[] = "mixer";
static constexpr std::size_t kSourceBufferLength = 4 * 1024;
static constexpr std::size_t kInputBufferLength = 4 * 1024;
static constexpr std::size_t kReformatBufferLength = 4 * 1024;
static constexpr std::size_t kResampleBufferLength = kReformatBufferLength;
static constexpr std::size_t kQuantisedBufferLength = 2 * 1024;
namespace audio {
SinkMixer::SinkMixer(StreamBufferHandle_t dest)
: commands_(xQueueCreate(1, sizeof(Args))),
is_idle_(xSemaphoreCreateBinary()),
resampler_(nullptr),
source_(xStreamBufferCreate(kSourceBufferLength, 1)),
sink_(dest) {
input_stream_.reset(new RawStream(kInputBufferLength));
floating_point_stream_.reset(new RawStream(kReformatBufferLength));
resampled_stream_.reset(new RawStream(kResampleBufferLength));
quantisation_buffer_ = {
reinterpret_cast<std::byte*>(heap_caps_malloc(
kQuantisedBufferLength, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)),
kQuantisedBufferLength};
quantisation_buffer_as_ints_ = {
reinterpret_cast<int*>(quantisation_buffer_.data()),
quantisation_buffer_.size_bytes() / 4};
quantisation_buffer_as_shorts_ = {
reinterpret_cast<short*>(quantisation_buffer_.data()),
quantisation_buffer_.size_bytes() / 2};
tasks::StartPersistent<tasks::Type::kMixer>([&]() { Main(); });
}
SinkMixer::~SinkMixer() {
vQueueDelete(commands_);
vSemaphoreDelete(is_idle_);
vStreamBufferDelete(source_);
heap_caps_free(quantisation_buffer_.data());
if (resampler_ != nullptr) {
src_delete(resampler_);
}
}
auto SinkMixer::MixAndSend(InputStream& input, const StreamInfo::Pcm& target)
-> std::size_t {
if (input.info().format_as<StreamInfo::Pcm>() !=
input_stream_->info().format_as<StreamInfo::Pcm>()) {
xSemaphoreTake(is_idle_, portMAX_DELAY);
Args args{
.cmd = Command::kSetSourceFormat,
.format = input.info().format_as<StreamInfo::Pcm>().value(),
};
xQueueSend(commands_, &args, portMAX_DELAY);
xSemaphoreGive(is_idle_);
}
if (target_format_ != target) {
xSemaphoreTake(is_idle_, portMAX_DELAY);
Args args{
.cmd = Command::kSetTargetFormat,
.format = target,
};
xQueueSend(commands_, &args, portMAX_DELAY);
xSemaphoreGive(is_idle_);
}
Args args{
.cmd = Command::kReadBytes,
.format = {},
};
xQueueSend(commands_, &args, portMAX_DELAY);
auto buf = input.data();
std::size_t bytes_sent =
xStreamBufferSend(source_, buf.data(), buf.size_bytes(), portMAX_DELAY);
input.consume(bytes_sent);
return bytes_sent;
}
auto SinkMixer::Main() -> void {
OutputStream input_receiver{input_stream_.get()};
xSemaphoreGive(is_idle_);
for (;;) {
Args args;
while (!xQueueReceive(commands_, &args, portMAX_DELAY)) {
}
switch (args.cmd) {
case Command::kSetSourceFormat:
ESP_LOGI(kTag, "setting source format");
input_receiver.prepare(args.format, {});
break;
case Command::kSetTargetFormat:
ESP_LOGI(kTag, "setting target format");
target_format_ = args.format;
break;
case Command::kReadBytes:
xSemaphoreTake(is_idle_, 0);
while (!xStreamBufferIsEmpty(source_)) {
auto buf = input_receiver.data();
std::size_t bytes_received = xStreamBufferReceive(
source_, buf.data(), buf.size_bytes(), portMAX_DELAY);
input_receiver.add(bytes_received);
HandleBytes();
}
xSemaphoreGive(is_idle_);
break;
}
}
}
auto SinkMixer::HandleBytes() -> void {
InputStream input{input_stream_.get()};
auto pcm = input.info().format_as<StreamInfo::Pcm>();
if (!pcm) {
ESP_LOGE(kTag, "mixer got unsupported data");
return;
}
if (*pcm == target_format_) {
// The happiest possible case: the input format matches the output
// format already. Streams like this should probably have bypassed the
// mixer.
// TODO(jacqueline): Make this an error; it's slow to use the mixer in this
// case, compared to just writing directly to the sink.
auto buf = input.data();
std::size_t bytes_sent =
xStreamBufferSend(sink_, buf.data(), buf.size_bytes(), portMAX_DELAY);
input.consume(bytes_sent);
return;
}
// Work out the resampling ratio using floating point arithmetic, since
// relying on the FPU for this will be much faster, and the difference in
// accuracy is unlikely to be noticeable.
float src_ratio = static_cast<float>(target_format_.sample_rate) /
static_cast<float>(pcm->sample_rate);
// Loop until we don't have any complete frames left in the input stream,
// where a 'frame' is one complete sample per channel.
while (!input_stream_->empty()) {
// The first step of both resampling and requantising is to convert the
// fixed point pcm input data into 32 bit floating point samples.
OutputStream floating_writer{floating_point_stream_.get()};
if (pcm->bits_per_sample == 16) {
ConvertFixedToFloating<short>(input, floating_writer);
} else {
// FIXME: We should consider treating 24 bit and 32 bit samples
// differently.
ConvertFixedToFloating<int>(input, floating_writer);
}
InputStream floating_reader{floating_point_stream_.get()};
while (!floating_point_stream_->empty()) {
RawStream* quantisation_source;
if (pcm->sample_rate != target_format_.sample_rate) {
// The input data needs to be resampled before being sent to the sink.
OutputStream resample_writer{resampled_stream_.get()};
Resample(src_ratio, pcm->channels, floating_reader, resample_writer);
quantisation_source = resampled_stream_.get();
} else {
// The input data already has an acceptable sample rate. All we need to
// do is quantise it.
quantisation_source = floating_point_stream_.get();
}
InputStream quantise_reader{quantisation_source};
while (!quantisation_source->empty()) {
std::size_t samples_available;
if (target_format_.bits_per_sample == 16) {
samples_available = Quantise<short>(quantise_reader);
} else {
samples_available = Quantise<int>(quantise_reader);
}
assert(samples_available * target_format_.real_bytes_per_sample() <=
quantisation_buffer_.size_bytes());
std::size_t bytes_sent = xStreamBufferSend(
sink_, quantisation_buffer_.data(),
samples_available * target_format_.real_bytes_per_sample(),
portMAX_DELAY);
assert(bytes_sent ==
samples_available * target_format_.real_bytes_per_sample());
}
}
}
}
template <>
auto SinkMixer::ConvertFixedToFloating<short>(InputStream& in_str,
OutputStream& out_str) -> void {
auto in = in_str.data_as<short>();
auto out = out_str.data_as<float>();
std::size_t samples_converted = std::min(in.size(), out.size());
src_short_to_float_array(in.data(), out.data(), samples_converted);
in_str.consume(samples_converted * sizeof(short));
out_str.add(samples_converted * sizeof(float));
}
template <>
auto SinkMixer::ConvertFixedToFloating<int>(InputStream& in_str,
OutputStream& out_str) -> void {
auto in = in_str.data_as<int>();
auto out = out_str.data_as<float>();
std::size_t samples_converted = std::min(in.size(), out.size());
src_int_to_float_array(in.data(), out.data(), samples_converted);
in_str.consume(samples_converted * sizeof(int));
out_str.add(samples_converted * sizeof(float));
}
auto SinkMixer::Resample(float src_ratio,
int channels,
InputStream& in,
OutputStream& out) -> void {
if (resampler_ == nullptr || src_get_channels(resampler_) != channels) {
if (resampler_ != nullptr) {
src_delete(resampler_);
}
ESP_LOGI(kTag, "creating new resampler with %u channels", channels);
int err = 0;
resampler_ = src_new(SRC_LINEAR, channels, &err);
assert(resampler_ != NULL);
assert(err == 0);
}
auto in_buf = in.data_as<float>();
auto out_buf = out.data_as<float>();
src_set_ratio(resampler_, src_ratio);
SRC_DATA args{
.data_in = in_buf.data(),
.data_out = out_buf.data(),
.input_frames = static_cast<long>(in_buf.size()),
.output_frames = static_cast<long>(out_buf.size()),
.input_frames_used = 0,
.output_frames_gen = 0,
.end_of_input = 0,
.src_ratio = src_ratio,
};
int err = src_process(resampler_, &args);
if (err != 0) {
ESP_LOGE(kTag, "resampler error: %s", src_strerror(err));
}
in.consume(args.input_frames_used * sizeof(float));
out.add(args.output_frames_gen * sizeof(float));
}
template <>
auto SinkMixer::Quantise<short>(InputStream& in) -> std::size_t {
auto src = in.data_as<float>();
cpp::span<short> dest = quantisation_buffer_as_shorts_;
dest = dest.first(std::min(src.size(), dest.size()));
src_float_to_short_array(src.data(), dest.data(), dest.size());
in.consume(dest.size() * sizeof(float));
return dest.size();
}
template <>
auto SinkMixer::Quantise<int>(InputStream& in) -> std::size_t {
auto src = in.data_as<float>();
cpp::span<int> dest = quantisation_buffer_as_ints_;
dest = dest.first(std::min<int>(src.size(), dest.size()));
src_float_to_int_array(src.data(), dest.data(), dest.size());
in.consume(dest.size() * sizeof(float));
return dest.size();
}
} // namespace audio

@ -5,7 +5,7 @@
namespace drivers { namespace drivers {
auto Bluetooth::Enable() -> Bluetooth* { auto Bluetooth::Enable() -> Bluetooth* {
return nullptr; return nullptr;
} }
} // namespace drivers } // namespace drivers

@ -161,7 +161,6 @@ auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
word_length = 0b10; word_length = 0b10;
break; break;
case BPS_32: case BPS_32:
// TODO(jacqueline): Error on this? It's not supported anymore.
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT; slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT;
slot_config_.ws_width = 32; slot_config_.ws_width = 32;
word_length = 0b11; word_length = 0b11;

@ -6,14 +6,15 @@
namespace drivers { namespace drivers {
class Bluetooth { class Bluetooth {
public: public:
static auto Enable() -> Bluetooth*; static auto Enable() -> Bluetooth*;
Bluetooth(); Bluetooth();
~Bluetooth(); ~Bluetooth();
struct Device {}; struct Device {};
auto Scan() -> std::vector<Device>; auto Scan() -> std::vector<Device>;
private:
};
} private:
};
} // namespace drivers

@ -51,14 +51,12 @@ class I2SDac {
BPS_32 = I2S_DATA_BIT_WIDTH_32BIT, BPS_32 = I2S_DATA_BIT_WIDTH_32BIT,
}; };
enum SampleRate { enum SampleRate {
SAMPLE_RATE_11_025 = 11025, SAMPLE_RATE_8 = 8000,
SAMPLE_RATE_16 = 16000,
SAMPLE_RATE_22_05 = 22050,
SAMPLE_RATE_32 = 32000, SAMPLE_RATE_32 = 32000,
SAMPLE_RATE_44_1 = 44100, SAMPLE_RATE_44_1 = 44100,
SAMPLE_RATE_48 = 48000, SAMPLE_RATE_48 = 48000,
SAMPLE_RATE_88_2 = 88200,
SAMPLE_RATE_96 = 96000, SAMPLE_RATE_96 = 96000,
SAMPLE_RATE_192 = 192000,
}; };
auto Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void; auto Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void;

@ -13,10 +13,10 @@
#include "database.hpp" #include "database.hpp"
#include "display.hpp" #include "display.hpp"
#include "gpios.hpp" #include "gpios.hpp"
#include "nvs.hpp"
#include "relative_wheel.hpp" #include "relative_wheel.hpp"
#include "samd.hpp" #include "samd.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "nvs.hpp"
#include "tag_parser.hpp" #include "tag_parser.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
#include "touchwheel.hpp" #include "touchwheel.hpp"

@ -34,6 +34,10 @@ auto Name<Type::kAudio>() -> std::string {
return "AUDIO"; return "AUDIO";
} }
template <> template <>
auto Name<Type::kMixer>() -> std::string {
return "MIXER";
}
template <>
auto Name<Type::kDatabase>() -> std::string { auto Name<Type::kDatabase>() -> std::string {
return "DB"; return "DB";
} }
@ -77,6 +81,14 @@ auto AllocateStack<Type::kFileStreamer>() -> cpp::span<StackType_t> {
size}; size};
} }
template <>
auto AllocateStack<Type::kMixer>() -> cpp::span<StackType_t> {
std::size_t size = 4 * 1024;
return {static_cast<StackType_t*>(
heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)),
size};
}
// Leveldb is designed for non-embedded use cases, where stack space isn't so // Leveldb is designed for non-embedded use cases, where stack space isn't so
// much of a concern. It therefore uses an eye-wateringly large amount of stack. // much of a concern. It therefore uses an eye-wateringly large amount of stack.
template <> template <>
@ -105,6 +117,10 @@ auto Priority() -> UBaseType_t;
// Realtime audio is the entire point of this device, so give this task the // Realtime audio is the entire point of this device, so give this task the
// highest priority. // highest priority.
template <> template <>
auto Priority<Type::kMixer>() -> UBaseType_t {
return 12;
}
template <>
auto Priority<Type::kAudio>() -> UBaseType_t { auto Priority<Type::kAudio>() -> UBaseType_t {
return 11; return 11;
} }

@ -36,6 +36,8 @@ enum class Type {
kFileStreamer, kFileStreamer,
// The main audio pipeline task. // The main audio pipeline task.
kAudio, kAudio,
// TODO
kMixer,
// Task for running database queries. // Task for running database queries.
kDatabase, kDatabase,
// Task for internal database operations // Task for internal database operations

Loading…
Cancel
Save