diff --git a/components/dsp_processor/dsp_processor.c b/components/dsp_processor/dsp_processor.c index 9ee4439..293ba7c 100644 --- a/components/dsp_processor/dsp_processor.c +++ b/components/dsp_processor/dsp_processor.c @@ -5,20 +5,14 @@ #include #include "freertos/FreeRTOS.h" -#if CONFIG_USE_DSP_PROCESSOR -#include "freertos/ringbuf.h" -#include "freertos/task.h" -#include "driver/i2s.h" +#if CONFIG_USE_DSP_PROCESSOR #include "dsps_biquad.h" #include "dsps_biquad_gen.h" #include "esp_log.h" +#include "freertos/queue.h" -#include "board_pins_config.h" -#include "driver/dac.h" -#include "driver/i2s.h" #include "dsp_processor.h" -#include "hal/i2s_hal.h" #ifdef CONFIG_USE_BIQUAD_ASM #define BIQUAD dsps_biquad_f32_ae32 @@ -28,23 +22,323 @@ static const char *TAG = "dspProc"; -static uint32_t currentSamplerate = 0; -static uint32_t currentChunkInFrames = 0; +#define DSP_PROCESSOR_LEN 16 -static ptype_t bq[12]; +static QueueHandle_t filterUpdateQHdl = NULL; + +static filterParams_t filterParams; + +static ptype_t *filter = NULL; static double dynamic_vol = 1.0; -#define DSP_PROCESSOR_LEN 16 +static bool init = false; -int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { +static float *sbuffer0 = NULL; +static float *sbufout0 = NULL; + +#if CONFIG_USE_DSP_PROCESSOR +#if CONFIG_SNAPCLIENT_DSP_FLOW_STEREO +dspFlows_t dspFlowInit = dspfStereo; +#endif +#if CONFIG_SNAPCLIENT_DSP_FLOW_BASSBOOST +dspFlows_t dspFlowInit = dspfBassBoost; +#endif +#if CONFIG_SNAPCLIENT_DSP_FLOW_BIAMP +dspFlows_t dspFlowInit = dspfBiamp; +#endif +#if CONFIG_SNAPCLIENT_DSP_FLOW_BASS_TREBLE_EQ +dspFlows_t dspFlowInit = dspfEQBassTreble; +#endif +#endif + +/** + * + */ +void dsp_processor_init(void) { + init = false; + + if (filterUpdateQHdl) { + vQueueDelete(filterUpdateQHdl); + filterUpdateQHdl = NULL; + } + + // have a max queue length of 1 here because we use xQueueOverwrite + // to write to the queue + filterUpdateQHdl = xQueueCreate(1, sizeof(filterParams_t)); + if (filterUpdateQHdl == NULL) { + ESP_LOGE(TAG, "%s: Failed to create filter update queue", __func__); + return; + } + + // TODO: load this data from NVM if available + filterParams.dspFlow = dspFlowInit; + + switch (filterParams.dspFlow) { + case dspfEQBassTreble: { + filterParams.fc_1 = 300.0; + filterParams.gain_1 = 0.0; + filterParams.fc_3 = 4000.0; + filterParams.gain_3 = 0.0; + + break; + } + + case dspfStereo: { + break; + } + + case dspfBassBoost: { + filterParams.fc_1 = 300.0; + filterParams.gain_1 = 6.0; + break; + } + + case dspfBiamp: { + filterParams.fc_1 = 300.0; + filterParams.gain_1 = 0; + filterParams.fc_3 = 100.0; + filterParams.gain_3 = 0.0; + break; + } + + case dspf2DOT1: { // Process audio L + R LOW PASS FILTER + ESP_LOGW(TAG, "dspf2DOT1, not implemented yet, using stereo instead"); + } break; + + case dspfFunkyHonda: { // Process audio L + R LOW PASS FILTER + ESP_LOGW(TAG, + "dspfFunkyHonda, not implemented yet, using stereo instead"); + break; + } + + default: { break; } + } + + ESP_LOGI(TAG, "%s: init done", __func__); +} + +/** + * free previously allocated memories + */ +void dsp_processor_uninit(void) { + if (sbuffer0) { + free(sbuffer0); + sbuffer0 = NULL; + } + + if (sbufout0) { + free(sbufout0); + sbufout0 = NULL; + } + + if (filter) { + free(filter); + filter = NULL; + } + + if (filterUpdateQHdl) { + vQueueDelete(filterUpdateQHdl); + filterUpdateQHdl = NULL; + } + + init = false; + + ESP_LOGI(TAG, "%s: uninit done", __func__); +} + +/** + * + */ +esp_err_t dsp_processor_update_filter_params(filterParams_t *params) { + if (filterUpdateQHdl) { + if (xQueueOverwrite(filterUpdateQHdl, params) == pdTRUE) { + return ESP_OK; + } + } + + return ESP_FAIL; +} + +/** + * + */ +static int32_t dsp_processor_gen_filter(ptype_t *filter, uint32_t cnt) { + if ((filter == NULL) && (cnt > 0)) { + return ESP_FAIL; + } + + for (int n = 0; n < cnt; n++) { + switch (filter[n].filtertype) { + case HIGHSHELF: + dsps_biquad_gen_highShelf_f32(filter[n].coeffs, filter[n].freq, + filter[n].gain, filter[n].q); + break; + + case LOWSHELF: + dsps_biquad_gen_lowShelf_f32(filter[n].coeffs, filter[n].freq, + filter[n].gain, filter[n].q); + break; + + case LPF: + dsps_biquad_gen_lpf_f32(filter[n].coeffs, filter[n].freq, filter[n].q); + break; + + case HPF: + dsps_biquad_gen_hpf_f32(filter[n].coeffs, filter[n].freq, filter[n].q); + break; + + default: + break; + } + // for (uint8_t i = 0; i <= 4; i++) { + // printf("%.6f ", filter[n].coeffs[i]); + // } + // printf("\n"); + } + + return ESP_OK; +} + +/** + * + */ +int dsp_processor_worker(char *audio, size_t chunk_size, uint32_t samplerate) { int16_t len = chunk_size / 4; int16_t valint; uint16_t i; - volatile uint32_t *audio_tmp = - (uint32_t *)audio; // volatile needed to ensure 32 bit access - float *sbuffer0 = NULL; - float *sbufout0 = NULL; + // volatile needed to ensure 32 bit access + volatile uint32_t *audio_tmp = (volatile uint32_t *)audio; + dspFlows_t dspFlow; + + // check if we need to update filters + if (xQueueReceive(filterUpdateQHdl, &filterParams, pdMS_TO_TICKS(0)) == + pdTRUE) { + init = false; + + // TODO: store filterParams in NVM + } + + dspFlow = filterParams.dspFlow; + + if (init == false) { + uint32_t cnt = 0; + + if (filter) { + free(filter); + filter = NULL; + } + + switch (dspFlow) { + case dspfEQBassTreble: { + cnt = 4; + + filter = + (ptype_t *)heap_caps_malloc(sizeof(ptype_t) * cnt, MALLOC_CAP_8BIT); + if (filter) { + // simple EQ control of low and high frequencies (bass, treble) + float bass_fc = filterParams.fc_1 / samplerate; + float bass_gain = filterParams.gain_1; + float treble_fc = filterParams.fc_3 / samplerate; + float treble_gain = filterParams.gain_3; + + // filters for CH 0 + filter[0] = (ptype_t){LOWSHELF, bass_fc, bass_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + filter[1] = (ptype_t){HIGHSHELF, treble_fc, treble_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + // filters for CH 1 + filter[2] = (ptype_t){LOWSHELF, bass_fc, bass_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + filter[3] = (ptype_t){HIGHSHELF, treble_fc, treble_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + + ESP_LOGI(TAG, "got new setting for dspfEQBassTreble"); + } else { + ESP_LOGE(TAG, "failed to get memory for filter"); + } + + break; + } + + case dspfStereo: { + cnt = 0; + break; + } + + case dspfBassBoost: { + cnt = 2; + + filter = + (ptype_t *)heap_caps_malloc(sizeof(ptype_t) * cnt, MALLOC_CAP_8BIT); + if (filter) { + float bass_fc = filterParams.fc_1 / samplerate; + float bass_gain = 6.0; + + filter[0] = (ptype_t){LOWSHELF, bass_fc, bass_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + filter[1] = (ptype_t){LOWSHELF, bass_fc, bass_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + + ESP_LOGI(TAG, "got new setting for dspfBassBoost"); + } else { + ESP_LOGE(TAG, "failed to get memory for filter"); + } + + break; + } + + case dspfBiamp: { + cnt = 4; + + filter = + (ptype_t *)heap_caps_malloc(sizeof(ptype_t) * cnt, MALLOC_CAP_8BIT); + if (filter) { + float lp_fc = filterParams.fc_1 / samplerate; + float lp_gain = filterParams.gain_1; + float hp_fc = filterParams.fc_3 / samplerate; + float hp_gain = filterParams.gain_3; + + filter[0] = (ptype_t){LPF, lp_fc, lp_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + filter[1] = (ptype_t){LPF, lp_fc, lp_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + filter[2] = (ptype_t){HPF, hp_fc, hp_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + filter[3] = (ptype_t){HPF, hp_fc, hp_gain, 0.707, + NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; + + ESP_LOGI(TAG, "got new setting for dspfBiamp"); + } else { + ESP_LOGE(TAG, "failed to get memory for filter"); + } + + break; + } + + case dspf2DOT1: { // Process audio L + R LOW PASS FILTER + cnt = 0; + dspFlow = dspfStereo; + + ESP_LOGW(TAG, "dspf2DOT1, not implemented yet, using stereo instead"); + } break; + + case dspfFunkyHonda: { // Process audio L + R LOW PASS FILTER + cnt = 0; + dspFlow = dspfStereo; + + ESP_LOGW(TAG, + "dspfFunkyHonda, not implemented yet, using stereo instead"); + break; + } + + default: { break; } + } + + dsp_processor_gen_filter(filter, cnt); + + init = true; + } // only process data if it is valid if (audio_tmp) { @@ -70,40 +364,46 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { case dspfEQBassTreble: { for (int k = 0; k < len; k += DSP_PROCESSOR_LEN) { volatile uint32_t *tmp = (uint32_t *)(&audio_tmp[k]); + uint32_t max = DSP_PROCESSOR_LEN; + uint32_t test = len - k; + + if (test < DSP_PROCESSOR_LEN) { + max = test; + } // channel 0 - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - sbuffer0[i] = dynamic_vol * 0.5 * - ((float)((int16_t)(tmp[i] & 0xFFFF))) / 32768; + for (i = 0; i < max; i++) { + sbuffer0[i] = dynamic_vol * /*0.5 **/ + ((float)((int16_t)(tmp[i] & 0xFFFF))) / INT16_MAX; } // BASS - BIQUAD(sbuffer0, sbufout0, DSP_PROCESSOR_LEN, bq[8].coeffs, bq[8].w); + BIQUAD(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); // TREBLE - BIQUAD(sbufout0, sbuffer0, DSP_PROCESSOR_LEN, bq[9].coeffs, bq[9].w); + BIQUAD(sbufout0, sbuffer0, max, filter[1].coeffs, filter[1].w); - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - valint = (int16_t)(sbuffer0[i] * 32768); - tmp[i] = (tmp[i] & 0xFFFF0000) + (uint32_t)valint; + for (i = 0; i < max; i++) { + valint = (int16_t)(sbuffer0[i] * INT16_MAX); + tmp[i] = + (volatile uint32_t)((tmp[i] & 0xFFFF0000) + (uint32_t)valint); } // channel 1 - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - sbuffer0[i] = dynamic_vol * 0.5 * + for (i = 0; i < max; i++) { + sbuffer0[i] = dynamic_vol * /*0.5 **/ ((float)((int16_t)((tmp[i] & 0xFFFF0000) >> 16))) / - 32768; + INT16_MAX; } // BASS - BIQUAD(sbuffer0, sbufout0, DSP_PROCESSOR_LEN, bq[10].coeffs, - bq[10].w); + BIQUAD(sbuffer0, sbufout0, max, filter[2].coeffs, filter[2].w); // TREBLE - BIQUAD(sbufout0, sbuffer0, DSP_PROCESSOR_LEN, bq[11].coeffs, - bq[11].w); + BIQUAD(sbufout0, sbuffer0, max, filter[3].coeffs, filter[3].w); - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - valint = (int16_t)(sbuffer0[i] * 32768); - tmp[i] = (tmp[i] & 0xFFFF) + ((uint32_t)valint << 16); + for (i = 0; i < max; i++) { + valint = (int16_t)(sbuffer0[i] * INT16_MAX); + tmp[i] = (volatile uint32_t)((tmp[i] & 0xFFFF) + + ((uint32_t)valint << 16)); } } @@ -113,10 +413,16 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { case dspfStereo: { for (int k = 0; k < len; k += DSP_PROCESSOR_LEN) { volatile uint32_t *tmp = (uint32_t *)(&audio_tmp[k]); + uint32_t max = DSP_PROCESSOR_LEN; + uint32_t test = len - k; + + if (test < DSP_PROCESSOR_LEN) { + max = test; + } // set volume if (dynamic_vol != 1.0) { - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { + for (i = 0; i < max; i++) { tmp[i] = ((uint32_t)(dynamic_vol * ((float)((int16_t)((tmp[i] & 0xFFFF0000) >> 16)))) @@ -133,29 +439,35 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { case dspfBassBoost: { // CH0 low shelf 6dB @ 400Hz for (int k = 0; k < len; k += DSP_PROCESSOR_LEN) { volatile uint32_t *tmp = (uint32_t *)(&audio_tmp[k]); + uint32_t max = DSP_PROCESSOR_LEN; + uint32_t test = len - k; + + if (test < DSP_PROCESSOR_LEN) { + max = test; + } // channel 0 - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { + for (i = 0; i < max; i++) { sbuffer0[i] = dynamic_vol * 0.5 * - ((float)((int16_t)(tmp[i] & 0xFFFF))) / 32768; + ((float)((int16_t)(tmp[i] & 0xFFFF))) / INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, DSP_PROCESSOR_LEN, bq[6].coeffs, bq[6].w); + BIQUAD(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - valint = (int16_t)(sbufout0[i] * 32768); + for (i = 0; i < max; i++) { + valint = (int16_t)(sbufout0[i] * INT16_MAX); tmp[i] = (tmp[i] & 0xFFFF0000) + (uint32_t)valint; } // channel 1 - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { + for (i = 0; i < max; i++) { sbuffer0[i] = dynamic_vol * 0.5 * ((float)((int16_t)((tmp[i] & 0xFFFF0000) >> 16))) / - 32768; + INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, DSP_PROCESSOR_LEN, bq[7].coeffs, bq[7].w); + BIQUAD(sbuffer0, sbufout0, max, filter[1].coeffs, filter[1].w); - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - valint = (int16_t)(sbufout0[i] * 32768); + for (i = 0; i < max; i++) { + valint = (int16_t)(sbufout0[i] * INT16_MAX); tmp[i] = (tmp[i] & 0xFFFF) + ((uint32_t)valint << 16); } } @@ -166,31 +478,37 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { case dspfBiamp: { for (int k = 0; k < len; k += DSP_PROCESSOR_LEN) { volatile uint32_t *tmp = (uint32_t *)(&audio_tmp[k]); + uint32_t max = DSP_PROCESSOR_LEN; + uint32_t test = len - k; + + if (test < DSP_PROCESSOR_LEN) { + max = test; + } // Process audio ch0 LOW PASS FILTER - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { + for (i = 0; i < max; i++) { sbuffer0[i] = dynamic_vol * 0.5 * - ((float)((int16_t)(tmp[i] & 0xFFFF))) / 32768; + ((float)((int16_t)(tmp[i] & 0xFFFF))) / INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, DSP_PROCESSOR_LEN, bq[0].coeffs, bq[0].w); - BIQUAD(sbufout0, sbuffer0, DSP_PROCESSOR_LEN, bq[1].coeffs, bq[1].w); + BIQUAD(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); + BIQUAD(sbufout0, sbuffer0, max, filter[1].coeffs, filter[1].w); - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - valint = (int16_t)(sbuffer0[i] * 32768); + for (i = 0; i < max; i++) { + valint = (int16_t)(sbuffer0[i] * INT16_MAX); tmp[i] = (tmp[i] & 0xFFFF0000) + (uint32_t)valint; } // Process audio ch1 HIGH PASS FILTER - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { + for (i = 0; i < max; i++) { sbuffer0[i] = dynamic_vol * 0.5 * ((float)((int16_t)((tmp[i] & 0xFFFF0000) >> 16))) / - 32768; + INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, DSP_PROCESSOR_LEN, bq[2].coeffs, bq[2].w); - BIQUAD(sbufout0, sbuffer0, DSP_PROCESSOR_LEN, bq[3].coeffs, bq[3].w); + BIQUAD(sbuffer0, sbufout0, max, filter[2].coeffs, filter[2].w); + BIQUAD(sbufout0, sbuffer0, max, filter[3].coeffs, filter[3].w); - for (i = 0; i < DSP_PROCESSOR_LEN; i++) { - valint = (int16_t)(sbuffer0[i] * 32768); + for (i = 0; i < max; i++) { + valint = (int16_t)(sbuffer0[i] * INT16_MAX); tmp[i] = (tmp[i] & 0xFFFF) + ((uint32_t)valint << 16); } } @@ -215,10 +533,10 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { for (uint16_t i = 0; i < len; i++) { valint[0] = (muteCH[0] == 1) ? (int16_t)0 : (int16_t)(sbufout0[i] * - 32768); valint[1] = (muteCH[1] == 1) ? (int16_t)0 : - (int16_t)(sbufout1[i] * 32768); valint[2] = (muteCH[2] == 1) ? - (int16_t)0 : (int16_t)(sbufout2[i] * 32768); dsp_audio[i * 4 + 0] = - (valint[2] & 0xff); dsp_audio[i * 4 + 1] = ((valint[2] & 0xff00) >> + INT16_MAX); valint[1] = (muteCH[1] == 1) ? (int16_t)0 : + (int16_t)(sbufout1[i] * INT16_MAX); valint[2] = (muteCH[2] == 1) ? + (int16_t)0 : (int16_t)(sbufout2[i] * INT16_MAX); dsp_audio[i * 4 + 0] + = (valint[2] & 0xff); dsp_audio[i * 4 + 1] = ((valint[2] & 0xff00) >> 8); dsp_audio[i * 4 + 2] = 0; dsp_audio[i * 4 + 3] = 0; dsp_audio1[i * 4 + 0] = (valint[0] & 0xff); @@ -250,7 +568,7 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { BIQUAD(sbuffer1, sbuftmp0, len, bq[4].coeffs, bq[4].w); BIQUAD(sbuftmp0, sbufout1, len, bq[5].coeffs, bq[5].w); - uint16_t scale = 16384; // 32768 + uint16_t scale = 16384; // INT16_MAX int16_t valint[5]; for (uint16_t i = 0; i < len; i++) { valint[0] = @@ -296,116 +614,37 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) { return 0; } -// ESP32 DSP processor -//====================================================== -// Each time a buffer of audio is passed to the DSP - samples are -// processed according to a dynamic list of audio processing nodes. +// void dsp_set_xoverfreq(uint8_t freqh, uint8_t freql, uint32_t samplerate) { +// float freq = freqh * 256 + freql; +// // printf("%f\n", freq); +// float f = freq / samplerate / 2.; +// for (int8_t n = 0; n <= 5; n++) { +// bq[n].freq = f; +// switch (bq[n].filtertype) { +// case LPF: +// // for (uint8_t i = 0; i <= 4; i++) { +// // printf("%.6f ", bq[n].coeffs[i]); +// // } +// // printf("\n"); +// dsps_biquad_gen_lpf_f32(bq[n].coeffs, bq[n].freq, bq[n].q); +// // for (uint8_t i = 0; i <= 4; i++) { +// // printf("%.6f ", bq[n].coeffs[i]); +// // } +// // printf("%f \n", bq[n].freq); +// break; +// case HPF: +// dsps_biquad_gen_hpf_f32(bq[n].coeffs, bq[n].freq, bq[n].q); +// break; +// default: +// break; +// } +// } +//} -// Each audio processor node consist of a data struct holding the -// required weights and states for processing an automomous processing -// function. The high level parameters is maintained in the structure -// as well - -// Release - Prove off concept -// ---------------------------------------- -// Fixed 2x2 biquad flow Xover for biAmp systems -// Interface for cross over frequency and level -void dsp_setup_flow(double freq, uint32_t samplerate, uint32_t chunkInFrames) { - float f = freq / samplerate / 2.0; - - if (((currentSamplerate == samplerate) && - (currentChunkInFrames == chunkInFrames)) || - (samplerate == 0) || (chunkInFrames == 0)) { - return; - } - - currentSamplerate = samplerate; - currentChunkInFrames = chunkInFrames; - - bq[0] = (ptype_t){LPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[1] = (ptype_t){LPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[2] = (ptype_t){HPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[3] = (ptype_t){HPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[4] = (ptype_t){HPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[5] = (ptype_t){HPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[6] = (ptype_t){LOWSHELF, f, 6, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[7] = (ptype_t){LOWSHELF, f, 6, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - - // TODO: make this (frequency and gain) dynamically adjustable - // test simple EQ control of low and high frequencies (bass, treble) - float bass_fc = 300.0 / samplerate; - float bass_gain = 6.0; - float treble_fc = 4000.0 / samplerate; - float treble_gain = 6.0; - // filters for CH 0 - bq[8] = (ptype_t){LOWSHELF, bass_fc, bass_gain, 0.707, - NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[9] = (ptype_t){HIGHSHELF, treble_fc, treble_gain, 0.707, - NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - // filters for CH 1 - bq[10] = (ptype_t){LOWSHELF, bass_fc, bass_gain, 0.707, - NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - bq[11] = (ptype_t){HIGHSHELF, treble_fc, treble_gain, 0.707, - NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}}; - - for (int n = 0; n < sizeof(bq) / sizeof(bq[0]); n++) { - switch (bq[n].filtertype) { - case HIGHSHELF: - dsps_biquad_gen_highShelf_f32(bq[n].coeffs, bq[n].freq, bq[n].gain, - bq[n].q); - break; - - case LOWSHELF: - dsps_biquad_gen_lowShelf_f32(bq[n].coeffs, bq[n].freq, bq[n].gain, - bq[n].q); - break; - - case LPF: - dsps_biquad_gen_lpf_f32(bq[n].coeffs, bq[n].freq, bq[n].q); - break; - - case HPF: - dsps_biquad_gen_hpf_f32(bq[n].coeffs, bq[n].freq, bq[n].q); - break; - - default: - break; - } - // for (uint8_t i = 0; i <= 4; i++) { - // printf("%.6f ", bq[n].coeffs[i]); - // } - // printf("\n"); - } -} - -void dsp_set_xoverfreq(uint8_t freqh, uint8_t freql, uint32_t samplerate) { - float freq = freqh * 256 + freql; - // printf("%f\n", freq); - float f = freq / samplerate / 2.; - for (int8_t n = 0; n <= 5; n++) { - bq[n].freq = f; - switch (bq[n].filtertype) { - case LPF: - // for (uint8_t i = 0; i <= 4; i++) { - // printf("%.6f ", bq[n].coeffs[i]); - // } - // printf("\n"); - dsps_biquad_gen_lpf_f32(bq[n].coeffs, bq[n].freq, bq[n].q); - // for (uint8_t i = 0; i <= 4; i++) { - // printf("%.6f ", bq[n].coeffs[i]); - // } - // printf("%f \n", bq[n].freq); - break; - case HPF: - dsps_biquad_gen_hpf_f32(bq[n].coeffs, bq[n].freq, bq[n].q); - break; - default: - break; - } - } -} - -void dsp_set_vol(double volume) { +/** + * + */ +void dsp_processor_set_volome(double volume) { if (volume >= 0 && volume <= 1.0) { ESP_LOGI(TAG, "Set volume to %f", volume); dynamic_vol = volume; diff --git a/components/dsp_processor/include/dsp_processor.h b/components/dsp_processor/include/dsp_processor.h index b3fe709..c663965 100644 --- a/components/dsp_processor/include/dsp_processor.h +++ b/components/dsp_processor/include/dsp_processor.h @@ -23,6 +23,10 @@ enum filtertypes { HIGHSHELF }; +// Each audio processor node consist of a data struct holding the +// required weights and states for processing an automomous processing +// function. The high level parameters is maintained in the structure +// as well // Process node typedef struct ptype { int filtertype; @@ -34,15 +38,28 @@ typedef struct ptype { float w[2]; } ptype_t; +// used to dynamically change used filters and their parameters +typedef struct filterParams_s { + dspFlows_t dspFlow; + float fc_1; + float gain_1; + float fc_2; + float gain_2; + float fc_3; + float gain_3; +} filterParams_t; + +// TODO: this is unused, remove??? // Process flow typedef struct pnode { ptype_t process; struct pnode *next; } pnode_t; -void dsp_setup_flow(double freq, uint32_t samplerate, uint32_t chunkInFrames); -int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow); -void dsp_set_xoverfreq(uint8_t, uint8_t, uint32_t); -void dsp_set_vol(double volume); +void dsp_processor_init(void); +void dsp_processor_uninit(void); +int dsp_processor_worker(char *audio, size_t chunk_size, uint32_t samplerate); +esp_err_t dsp_processor_update_filter_params(filterParams_t *params); +void dsp_processor_set_volome(double volume); #endif /* _DSP_PROCESSOR_H_ */ diff --git a/components/lightsnapcast/player.c b/components/lightsnapcast/player.c index 6b5371e..737f4a8 100644 --- a/components/lightsnapcast/player.c +++ b/components/lightsnapcast/player.c @@ -950,6 +950,8 @@ int32_t insert_pcm_chunk(pcm_chunk_message_t *pcmChunk) { if (pcmChkQHdl == NULL) { ESP_LOGW(TAG, "pcm chunk queue not created"); + free_pcm_chunk(pcmChunk); + return -2; } @@ -1398,22 +1400,19 @@ static void player_task(void *pvParameters) { usec = diff2Server - sec * 1000000; msec = usec / 1000; usec = usec % 1000; + // ESP_LOGI (TAG, "%d, %lldus, %lldus %llds, %lld.%lldms", - // dir, age, avg, sec, msec, usec); ESP_LOGI(TAG, "%d, - // %lldus, %lldus, %lldus, q:%d", dir, avg, shortMedian, - // miniMedian, uxQueueMessagesWaiting(pcmChkQHdl)); - // ESP_LOGI( TAG, "8b f %d b %d", - // heap_caps_get_free_size(MALLOC_CAP_8BIT | - // MALLOC_CAP_INTERNAL), - // heap_caps_get_largest_free_block - // (MALLOC_CAP_8BIT | - // MALLOC_CAP_INTERNAL)); - // ESP_LOGI( TAG, "32b f %d b %d", - // heap_caps_get_free_size(MALLOC_CAP_32BIT | - // MALLOC_CAP_EXEC), - // heap_caps_get_largest_free_block - // (MALLOC_CAP_32BIT | - // MALLOC_CAP_EXEC)); + // dir, age, avg, sec, msec, usec); + // ESP_LOGI(TAG, "%d, %lldus, %lldus, %lldus, q:%d", dir, avg, + // shortMedian, miniMedian, uxQueueMessagesWaiting(pcmChkQHdl)); + // ESP_LOGI( TAG, "8b f %d b %d", + // heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL), + // heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | + // MALLOC_CAP_INTERNAL)); + // ESP_LOGI( TAG, "32b f %d b %d", + // heap_caps_get_free_size(MALLOC_CAP_32BIT | + // MALLOC_CAP_EXEC), heap_caps_get_largest_free_block + // (MALLOC_CAP_32BIT | MALLOC_CAP_EXEC)); } dir = 0; diff --git a/components/ui_http_server/CMakeLists.txt b/components/ui_http_server/CMakeLists.txt new file mode 100644 index 0000000..9b27304 --- /dev/null +++ b/components/ui_http_server/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register(SRCS "ui_http_server.c" + INCLUDE_DIRS "include" + REQUIRES spiffs esp_http_server mbedtls dsp_processor) + +# Create a SPIFFS image from the contents of the 'html' directory +# that fits the partition named 'storage'. FLASH_IN_PROJECT indicates that +# the generated image should be flashed when the entire project is flashed to +# the target with 'idf.py -p PORT flash +spiffs_create_partition_image(storage html FLASH_IN_PROJECT) diff --git a/components/ui_http_server/Kconfig.projbuild b/components/ui_http_server/Kconfig.projbuild new file mode 100644 index 0000000..0f48900 --- /dev/null +++ b/components/ui_http_server/Kconfig.projbuild @@ -0,0 +1,32 @@ +menu "Application Configuration" + + menu "HTTP Server Setting" + + config WEB_PORT + int "HTTP Server Port" + default 8000 + help + HTTP server port to use. + + endmenu + + menu "GPIO Setting" + + config GPIO_RANGE_MAX + int + default 33 if IDF_TARGET_ESP32 + default 46 if IDF_TARGET_ESP32S2 + default 48 if IDF_TARGET_ESP32S3 + default 19 if IDF_TARGET_ESP32C3 + + config BLINK_GPIO + int "Blink GPIO number" + range 0 GPIO_RANGE_MAX + default 5 + help + GPIO number (IOxx) to blink on and off or the RMT signal for the addressable LED. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink. + + endmenu + +endmenu diff --git a/components/ui_http_server/html/ESP-LOGO.txt b/components/ui_http_server/html/ESP-LOGO.txt new file mode 100644 index 0000000..9e0bb2b --- /dev/null +++ b/components/ui_http_server/html/ESP-LOGO.txt @@ -0,0 +1,503 @@ +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 +Dkkk/x8EAts5ViTnJwAAAABJRU5ErkJggg== diff --git a/components/ui_http_server/html/favicon.ico b/components/ui_http_server/html/favicon.ico new file mode 100644 index 0000000..17b8ff3 Binary files /dev/null and b/components/ui_http_server/html/favicon.ico differ diff --git a/components/ui_http_server/html/index.html b/components/ui_http_server/html/index.html new file mode 100644 index 0000000..65fc227 --- /dev/null +++ b/components/ui_http_server/html/index.html @@ -0,0 +1,36 @@ + + + + + + + + +

DSP processor filter configuration

+ +

base gain

+ + +

50 %

+

+ + + diff --git a/components/ui_http_server/image/ESP-LOGO.png b/components/ui_http_server/image/ESP-LOGO.png new file mode 100644 index 0000000..1bf46dd Binary files /dev/null and b/components/ui_http_server/image/ESP-LOGO.png differ diff --git a/components/ui_http_server/include/ui_http_server.h b/components/ui_http_server/include/ui_http_server.h new file mode 100644 index 0000000..091be1d --- /dev/null +++ b/components/ui_http_server/include/ui_http_server.h @@ -0,0 +1,11 @@ +#ifndef __UI_HTTP_SERVER_H__ +#define __UI_HTTP_SERVER_H__ + +void init_http_server_task(void); + +typedef struct { + char str_value[4]; + long long_value; +} URL_t; + +#endif // __UI_HTTP_SERVER_H__ diff --git a/components/ui_http_server/ui_http_server.c b/components/ui_http_server/ui_http_server.c new file mode 100644 index 0000000..cfe2aaa --- /dev/null +++ b/components/ui_http_server/ui_http_server.c @@ -0,0 +1,421 @@ +/* HTTP Server Example + + This example code is in the Public Domain (or CC0 licensed, at your + option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" + +#include "esp_err.h" +#include "esp_http_server.h" +#include "esp_log.h" +#include "esp_spiffs.h" +#include "esp_vfs.h" +#include "esp_wifi.h" + +#include "dsp_processor.h" +#include "ui_http_server.h" + +static const char *TAG = "HTTP"; + +static QueueHandle_t xQueueHttp; + +/** + * + */ +static void SPIFFS_Directory(char *path) { + DIR *dir = opendir(path); + assert(dir != NULL); + while (true) { + struct dirent *pe = readdir(dir); + if (!pe) break; + ESP_LOGI(TAG, "d_name=%s/%s d_ino=%d d_type=%x", path, pe->d_name, + pe->d_ino, pe->d_type); + } + closedir(dir); +} + +/** + * + */ +static esp_err_t SPIFFS_Mount(char *path, char *label, int max_files) { + esp_vfs_spiffs_conf_t conf = {.base_path = path, + .partition_label = label, + .max_files = max_files, + .format_if_mount_failed = true}; + + // Use settings defined above to initialize and mount SPIFFS file system. + // Note: esp_vfs_spiffs_register is an all-in-one convenience function. + esp_err_t ret = esp_vfs_spiffs_register(&conf); + + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + ESP_LOGE(TAG, "Failed to mount or format filesystem"); + } else if (ret == ESP_ERR_NOT_FOUND) { + ESP_LOGE(TAG, "Failed to find SPIFFS partition"); + } else { + ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); + } + return ret; + } + + size_t total = 0, used = 0; + ret = esp_spiffs_info(conf.partition_label, &total, &used); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", + esp_err_to_name(ret)); + } else { + ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); + } + + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Mount %s to %s success", path, label); + SPIFFS_Directory(path); + } + + return ret; +} + +/** + * + */ +static int find_key_value(char *key, char *parameter, char *value) { + // char * addr1; + char *addr1 = strstr(parameter, key); + if (addr1 == NULL) return 0; + ESP_LOGD(TAG, "addr1=%s", addr1); + + char *addr2 = addr1 + strlen(key); + ESP_LOGD(TAG, "addr2=[%s]", addr2); + + char *addr3 = strstr(addr2, "&"); + ESP_LOGD(TAG, "addr3=%p", addr3); + if (addr3 == NULL) { + strcpy(value, addr2); + } else { + int length = addr3 - addr2; + ESP_LOGD(TAG, "addr2=%p addr3=%p length=%d", addr2, addr3, length); + strncpy(value, addr2, length); + value[length] = 0; + } + // ESP_LOGI(TAG, "key=[%s] value=[%s]", key, value); + return strlen(value); +} + +/** + * + */ +static esp_err_t Text2Html(httpd_req_t *req, char *filename) { + // ESP_LOGI(TAG, "Reading %s", filename); + FILE *fhtml = fopen(filename, "r"); + if (fhtml == NULL) { + ESP_LOGE(TAG, "fopen fail. [%s]", filename); + return ESP_FAIL; + } else { + char line[128]; + while (fgets(line, sizeof(line), fhtml) != NULL) { + size_t linelen = strlen(line); + // remove EOL (CR or LF) + for (int i = linelen; i > 0; i--) { + if (line[i - 1] == 0x0a) { + line[i - 1] = 0; + } else if (line[i - 1] == 0x0d) { + line[i - 1] = 0; + } else { + break; + } + } + ESP_LOGD(TAG, "line=[%s]", line); + if (strlen(line) == 0) continue; + esp_err_t ret = httpd_resp_sendstr_chunk(req, line); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "httpd_resp_sendstr_chunk fail %d", ret); + } + } + fclose(fhtml); + } + return ESP_OK; +} + +/** + * + */ +static esp_err_t Image2Html(httpd_req_t *req, char *filename, char *type) { + FILE *fhtml = fopen(filename, "r"); + if (fhtml == NULL) { + ESP_LOGE(TAG, "fopen fail. [%s]", filename); + return ESP_FAIL; + } else { + char buffer[64]; + + if (strcmp(type, "jpeg") == 0) { + httpd_resp_sendstr_chunk(req, " 0) { + httpd_resp_send_chunk(req, buffer, bufferSize); + } else { + break; + } + } + fclose(fhtml); + httpd_resp_sendstr_chunk(req, "\">"); + } + return ESP_OK; +} + +/** + * HTTP get handler + */ +static esp_err_t root_get_handler(httpd_req_t *req) { + // ESP_LOGI(TAG, "root_get_handler req->uri=[%s]", req->uri); + + /* Send index.html */ + Text2Html(req, "/html/index.html"); + + /* Send Image */ + Image2Html(req, "/html/ESP-LOGO.txt", "png"); + + /* Send empty chunk to signal HTTP response completion */ + httpd_resp_sendstr_chunk(req, NULL); + + return ESP_OK; +} + +/* + * HTTP post handler + */ +static esp_err_t root_post_handler(httpd_req_t *req) { + // ESP_LOGI(TAG, "root_post_handler req->uri=[%s]", req->uri); + URL_t urlBuf; + find_key_value("value=", (char *)req->uri, urlBuf.str_value); + // ESP_LOGD(TAG, "urlBuf.str_value=[%s]", urlBuf.str_value); + urlBuf.long_value = strtol(urlBuf.str_value, NULL, 10); + // ESP_LOGD(TAG, "urlBuf.long_value=%ld", urlBuf.long_value); + + // Send to http_server_task + if (xQueueSend(xQueueHttp, &urlBuf, portMAX_DELAY) != pdPASS) { + ESP_LOGE(TAG, "xQueueSend Fail"); + } + + /* Redirect onto root to see the updated file list */ + httpd_resp_set_status(req, "303 See Other"); + httpd_resp_set_hdr(req, "Location", "/"); +#ifdef CONFIG_EXAMPLE_HTTPD_CONN_CLOSE_HEADER + httpd_resp_set_hdr(req, "Connection", "close"); +#endif + httpd_resp_sendstr(req, "post successfully"); + return ESP_OK; +} + +/* + * favicon get handler + */ +static esp_err_t favicon_get_handler(httpd_req_t *req) { + // ESP_LOGI(TAG, "favicon_get_handler req->uri=[%s]", req->uri); + return ESP_OK; +} + +/* + * Function to start the web server + */ +esp_err_t start_server(const char *base_path, int port) { + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = port; + config.max_open_sockets = 2; + + /* Use the URI wildcard matching function in order to + * allow the same handler to respond to multiple different + * target URIs which match the wildcard scheme */ + config.uri_match_fn = httpd_uri_match_wildcard; + + ESP_LOGI(TAG, "Starting HTTP Server on port: '%d'", config.server_port); + if (httpd_start(&server, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start file server!"); + return ESP_FAIL; + } + + /* URI handler for get */ + httpd_uri_t _root_get_handler = { + .uri = "/", .method = HTTP_GET, .handler = root_get_handler, + //.user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &_root_get_handler); + + /* URI handler for post */ + httpd_uri_t _root_post_handler = { + .uri = "/post", .method = HTTP_POST, .handler = root_post_handler, + //.user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &_root_post_handler); + + /* URI handler for favicon.ico */ + httpd_uri_t _favicon_get_handler = { + .uri = "/favicon.ico", .method = HTTP_GET, .handler = favicon_get_handler, + //.user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &_favicon_get_handler); + + return ESP_OK; +} + +//// LEDC Stuff +//#define LEDC_TIMER LEDC_TIMER_0 +//#define LEDC_MODE LEDC_LOW_SPEED_MODE +////#define LEDC_OUTPUT_IO (5) // Define the output GPIO +//#define LEDC_OUTPUT_IO CONFIG_BLINK_GPIO // Define the output +// GPIO #define LEDC_CHANNEL LEDC_CHANNEL_0 #define LEDC_DUTY_RES +// LEDC_TIMER_13_BIT // Set duty resolution to 13 bits #define LEDC_DUTY +//(4095) // Set duty to 50%. ((2 ** 13) - 1) * 50% = 4095 #define LEDC_FREQUENCY +//(5000) // Frequency in Hertz. Set frequency at 5 kHz +// +// static void ledc_init(void) +//{ +// // Prepare and then apply the LEDC PWM timer configuration +// ledc_timer_config_t ledc_timer = { +// .speed_mode = LEDC_MODE, +// .timer_num = LEDC_TIMER, +// .duty_resolution = LEDC_DUTY_RES, +// .freq_hz = LEDC_FREQUENCY, // Set output +// frequency at 5 kHz .clk_cfg = LEDC_AUTO_CLK +// }; +// ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); +// +// // Prepare and then apply the LEDC PWM channel configuration +// ledc_channel_config_t ledc_channel = { +// .speed_mode = LEDC_MODE, +// .channel = LEDC_CHANNEL, +// .timer_sel = LEDC_TIMER, +// .intr_type = LEDC_INTR_DISABLE, +// .gpio_num = LEDC_OUTPUT_IO, +// .duty = 0, // Set duty to 0% +// .hpoint = 0 +// }; +// ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); +//} + +/** + * + */ +static void http_server_task(void *pvParameters) { + /* Get the local IP address */ + esp_netif_ip_info_t ip_info; + ESP_ERROR_CHECK(esp_netif_get_ip_info( + esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info)); + + char ipString[64]; + sprintf(ipString, IPSTR, IP2STR(&ip_info.ip)); + + ESP_LOGI(TAG, "Start http task=%s", ipString); + + char portString[6]; + sprintf(portString, "%d", CONFIG_WEB_PORT); + + char url[strlen("http://") + strlen(ipString) + strlen(":") + + strlen(portString) + 1]; + memset(url, 0, sizeof(url)); + strcat(url, ipString); + strcat(url, ":"); + strcat(url, portString); + + // Set the LEDC peripheral configuration + // ledc_init(); + + // Set duty to 50% + // double maxduty = pow(2, 13) - 1; + // float percent = 0.5; + // uint32_t duty = maxduty * percent; + // ESP_LOGI(TAG, "duty=%"PRIu32, duty); + // ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, LEDC_DUTY)); + // ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty)); + // Update duty to apply the new value + // ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL)); + + // Start Server + ESP_LOGI(TAG, "Starting server on %s", url); + ESP_ERROR_CHECK(start_server("/html", CONFIG_WEB_PORT)); + + URL_t urlBuf; + while (1) { + // ESP_LOGW (TAG, "stack free: %d", uxTaskGetStackHighWaterMark(NULL)); + + // Waiting for post + if (xQueueReceive(xQueueHttp, &urlBuf, portMAX_DELAY) == pdTRUE) { + filterParams_t filterParams; + float scale, gainMax = 6.0; + + // ESP_LOGI(TAG, "str_value=%s long_value=%ld", urlBuf.str_value, + // urlBuf.long_value); + + scale = ((float)(2 * urlBuf.long_value - 50) / 100.0); + + filterParams.dspFlow = dspfEQBassTreble; + filterParams.fc_1 = 300.0; + filterParams.gain_1 = gainMax * scale; + filterParams.fc_3 = 4000.0; + filterParams.gain_3 = gainMax * 0; + +#if CONFIG_USE_DSP_PROCESSOR + dsp_processor_update_filter_params(&filterParams); +#endif + + // Set duty value + // percent = urlBuf.long_value / 100.0; + // duty = maxduty * percent; + // ESP_LOGI(TAG, "percent=%f duty=%"PRIu32, + // percent, duty); + // ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty)); + // Update duty to apply the new value + // ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, + // LEDC_CHANNEL)); + } + } + + // Never reach here + ESP_LOGI(TAG, "finish"); + vTaskDelete(NULL); +} + +/** + * + */ +void init_http_server_task(void) { + // Initialize SPIFFS + ESP_LOGI(TAG, "Initializing SPIFFS"); + if (SPIFFS_Mount("/html", "storage", 6) != ESP_OK) { + ESP_LOGE(TAG, "SPIFFS mount failed"); + while (1) { + vTaskDelay(1); + } + } + + // Create Queue + xQueueHttp = xQueueCreate(10, sizeof(URL_t)); + configASSERT(xQueueHttp); + + xTaskCreatePinnedToCore(http_server_task, "HTTP", 512 * 5, NULL, 2, NULL, + tskNO_AFFINITY); +} diff --git a/main/main.c b/main/main.c index 01340f6..6d8a757 100644 --- a/main/main.c +++ b/main/main.c @@ -51,6 +51,8 @@ #include "player.h" #include "snapcast.h" +#include "ui_http_server.h" + static FLAC__StreamDecoderReadStatus read_callback( const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); @@ -303,8 +305,9 @@ static FLAC__StreamDecoderReadStatus read_callback( return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } -static flacData_t flacOutData; - +/** + * + */ static FLAC__StreamDecoderWriteStatus write_callback( const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) { @@ -312,7 +315,6 @@ static FLAC__StreamDecoderWriteStatus write_callback( flacData_t *flacData = NULL; // = &flacOutData; snapcastSetting_t *scSet = (snapcastSetting_t *)client_data; int ret = 0; - uint32_t tmpData; uint32_t fragmentCnt = 0; (void)decoder; @@ -321,8 +323,8 @@ static FLAC__StreamDecoderWriteStatus write_callback( // xQueueReceive (flacReadQHdl, &flacData, portMAX_DELAY); - // ESP_LOGI(TAG, "in flac write cb %d %p", frame->header.blocksize, - // flacData); + // ESP_LOGI(TAG, "in flac write cb %d %p", frame->header.blocksize, + // flacData); if (frame->header.channels != scSet->ch) { ESP_LOGE(TAG, @@ -357,8 +359,6 @@ static FLAC__StreamDecoderWriteStatus write_callback( flacData->bytes = frame->header.blocksize * frame->header.channels * (frame->header.bits_per_sample / 8); - // flacData->outData = (char *)realloc (flacData->outData, flacData->bytes); - // flacData->outData = (char *)malloc (flacData->bytes); ret = allocate_pcm_chunk_memory(&(flacData->outData), flacData->bytes); // ESP_LOGI (TAG, "mem %p %p %d", flacData->outData, @@ -379,14 +379,16 @@ static FLAC__StreamDecoderWriteStatus write_callback( // TODO: for now fragmented payload is not supported and the whole // chunk is expected to be in the first fragment + uint32_t tmpData; tmpData = ((uint32_t)((buffer[0][i] >> 8) & 0xFF) << 24) | ((uint32_t)((buffer[0][i] >> 0) & 0xFF) << 16) | ((uint32_t)((buffer[1][i] >> 8) & 0xFF) << 8) | ((uint32_t)((buffer[1][i] >> 0) & 0xFF) << 0); if (fragment != NULL) { - uint32_t *test = (uint32_t *)(&(fragment->payload[fragmentCnt])); - *test = tmpData; + volatile uint32_t *test = + (volatile uint32_t *)(&(fragment->payload[fragmentCnt])); + *test = (volatile uint32_t *)tmpData; } fragmentCnt += 4; @@ -411,6 +413,9 @@ static FLAC__StreamDecoderWriteStatus write_callback( return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } +/** + * + */ void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) { @@ -449,6 +454,9 @@ void metadata_callback(const FLAC__StreamDecoder *decoder, // xSemaphoreGive(flacReadSemaphore); } +/** + * + */ void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) { (void)decoder, (void)client_data; @@ -600,17 +608,17 @@ void flac_task(void *pvParameters) { ((double)scSet->volume / 100 / (20 - flow_drain_counter)); if (flow_drain_counter == 0) { #if SNAPCAST_USE_SOFT_VOL - dynamic_vol = 0; + dynamic_vol = 0.0; #else - dynamic_vol = 1; + dynamic_vol = 1.0; #endif audio_hal_set_mute(board_handle->audio_hal, scSet->muted); } - dsp_set_vol(dynamic_vol); + dsp_processor_set_volome(dynamic_vol); } - dsp_setup_flow(500, scSet->sr, scSet->chkInFrames); - dsp_processor(pcmData->fragment->payload, pcmData->fragment->size, - dspFlow); + + dsp_processor_worker(pcmData->fragment->payload, + pcmData->fragment->size, scSet->sr); #endif insert_pcm_chunk(pcmData); @@ -663,7 +671,7 @@ static void http_get_task(void *pvParameters) { snapcastSetting_t scSet; // flacData_t flacData = {SNAPCAST_MESSAGE_CODEC_HEADER, NULL, {0, 0}, NULL, // 0}; - flacData_t *pFlacData; + flacData_t *pFlacData = NULL; pcm_chunk_message_t *pcmData = NULL; ip_addr_t remote_ip; uint16_t remotePort = 0; @@ -1287,11 +1295,11 @@ static void http_get_task(void *pvParameters) { internalState++; - // ESP_LOGI(TAG, - // "got wire chunk with size: %d, at time" - // " %d.%d", wire_chnk.size, - // wire_chnk.timestamp.sec, - // wire_chnk.timestamp.usec); + // ESP_LOGI(TAG, + // "chunk with size: %d, at time" + // " %d.%d", wire_chnk.size, + // wire_chnk.timestamp.sec, + // wire_chnk.timestamp.usec); break; } @@ -1305,23 +1313,59 @@ static void http_get_task(void *pvParameters) { tmp = len; } + // static double lastChunkTimestamp = + // 0; double timestamp = + // ((double)wire_chnk.timestamp.sec * + // 1000000.0 + + // (double)wire_chnk.timestamp.usec) + // / 1000.0; + // + // ESP_LOGI(TAG, "duration %lfms, + // length %d", timestamp - + // lastChunkTimestamp, tmp); + // + // lastChunkTimestamp = timestamp; + if (received_header == true) { switch (codec) { case FLAC: { #if TEST_DECODER_TASK - pFlacData = - (flacData_t *)malloc(sizeof(flacData_t)); + pFlacData = NULL; + while (!pFlacData) { + pFlacData = + (flacData_t *)malloc(sizeof(flacData_t)); + if (!pFlacData) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + pFlacData->bytes = tmp; + // store timestamp for // later use pFlacData->timestamp = wire_chnk.timestamp; - pFlacData->inData = (char *)malloc(tmp); - memcpy(pFlacData->inData, start, tmp); - pFlacData->outData = NULL; - pFlacData->type = SNAPCAST_MESSAGE_WIRE_CHUNK; + pFlacData->inData = NULL; - // send data to seperate task which will handle this - xQueueSend(flacTaskQHdl, &pFlacData, portMAX_DELAY); + // while ((!pFlacData->inData) && (mallocCnt < 100)) + // { + while (!pFlacData->inData) { + pFlacData->inData = + (char *)malloc(pFlacData->bytes); + if (!pFlacData->inData) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + if (pFlacData->inData) { + memcpy(pFlacData->inData, start, tmp); + pFlacData->outData = NULL; + pFlacData->type = SNAPCAST_MESSAGE_WIRE_CHUNK; + + // send data to seperate task which will handle + // this + xQueueSend(flacTaskQHdl, &pFlacData, + portMAX_DELAY); + } #else flacData.bytes = tmp; flacData.timestamp = @@ -1375,8 +1419,9 @@ static void http_get_task(void *pvParameters) { remainderSize = 0; } - if (pcmData != NULL) { - uint32_t *sample; + // if (pcmData != NULL) + { + volatile uint32_t *sample; int max = 0, begin = 0; @@ -1420,9 +1465,11 @@ static void http_get_task(void *pvParameters) { dummy2 |= (uint32_t)dummy1 << 8; tmpData = dummy2; - sample = (uint32_t *)(&( - pcmData->fragment->payload[offset])); - *sample = tmpData; + if ((pcmData) && (pcmData->fragment->payload)) { + sample = (volatile uint32_t *)(&( + pcmData->fragment->payload[offset])); + *sample = (volatile uint32_t)tmpData; + } offset += 4; } @@ -1442,11 +1489,12 @@ static void http_get_task(void *pvParameters) { ((uint32_t)start[i + 2] << 0) | ((uint32_t)start[i + 3] << 8); - // ensure 32bit alligned - // write - sample = (uint32_t *)(&( - pcmData->fragment->payload[offset])); - *sample = tmpData; + // ensure 32bit aligned write + if ((pcmData) && (pcmData->fragment->payload)) { + sample = (volatile uint32_t *)(&( + pcmData->fragment->payload[offset])); + *sample = (volatile uint32_t)tmpData; + } offset += 4; } @@ -1556,20 +1604,24 @@ static void http_get_task(void *pvParameters) { (20 - flow_drain_counter)); if (flow_drain_counter == 0) { #if SNAPCAST_USE_SOFT_VOL - dynamic_vol = 0; + dynamic_vol = 0.0; #else - dynamic_vol = 1; + dynamic_vol = 1.0; #endif audio_hal_set_mute( board_handle->audio_hal, server_settings_message.muted); } - dsp_set_vol(dynamic_vol); + + dsp_processor_set_volome(dynamic_vol); } - dsp_setup_flow(500, scSet.sr, - scSet.chkInFrames); - dsp_processor(pcmData->fragment->payload, - pcmData->fragment->size, dspFlow); + + if ((pcmData) && (pcmData->fragment->payload)) { + dsp_processor_worker( + pcmData->fragment->payload, + pcmData->fragment->size, scSet.sr); + } + #endif insert_pcm_chunk(pcmData); @@ -1585,9 +1637,11 @@ static void http_get_task(void *pvParameters) { } case PCM: { - size_t decodedSize = pcmData->fragment->size; + size_t decodedSize = wire_chnk.size; - pcmData->timestamp = wire_chnk.timestamp; + if (pcmData) { + pcmData->timestamp = wire_chnk.timestamp; + } scSet.chkInFrames = decodedSize / @@ -1612,23 +1666,31 @@ static void http_get_task(void *pvParameters) { (20 - flow_drain_counter)); if (flow_drain_counter == 0) { #if SNAPCAST_USE_SOFT_VOL - dynamic_vol = 0; + dynamic_vol = 0.0; #else - dynamic_vol = 1; + dynamic_vol = 1.0; #endif audio_hal_set_mute( board_handle->audio_hal, server_settings_message.muted); } - dsp_set_vol(dynamic_vol); + + dsp_processor_set_volome(dynamic_vol); + } + + if ((pcmData) && (pcmData->fragment->payload)) { + dsp_processor_worker(pcmData->fragment->payload, + pcmData->fragment->size, + scSet.sr); } - dsp_setup_flow(500, scSet.sr, scSet.chkInFrames); - dsp_processor(pcmData->fragment->payload, - pcmData->fragment->size, dspFlow); #endif - insert_pcm_chunk(pcmData); + if (pcmData) { + insert_pcm_chunk(pcmData); + } + pcmData = NULL; + break; } @@ -2204,10 +2266,10 @@ static void http_get_task(void *pvParameters) { audio_hal_set_mute(board_handle->audio_hal, server_settings_message.muted); #if SNAPCAST_USE_SOFT_VOL - dsp_set_vol((double)server_settings_message.volume / - 100); + dsp_processor_set_volome( + (double)server_settings_message.volume / 100); #else - dsp_set_vol(1.0); + dsp_processor_set_volome(1.0); #endif } #else @@ -2217,8 +2279,8 @@ static void http_get_task(void *pvParameters) { } if (scSet.volume != server_settings_message.volume) { #if SNAPCAST_USE_SOFT_VOL - dsp_set_vol((double)server_settings_message.volume / - 100); + dsp_processor_set_volome( + (double)server_settings_message.volume / 100); #else audio_hal_set_volume(board_handle->audio_hal, server_settings_message.volume); @@ -2615,19 +2677,18 @@ void app_main(void) { i2s_mclk_gpio_select(0, 0); // setup_ma120(); -#if CONFIG_USE_DSP_PROCESSOR - dsp_setup_flow(500, 44100, 20); // init with default value -#endif - ESP_LOGI(TAG, "init player"); init_player(); // Enable and setup WIFI in station mode and connect to Access point setup in // menu config or set up provisioning mode settable in menuconfig wifi_init(); + ESP_LOGI(TAG, "Connected to AP"); + + // http server for control operations and user interface + init_http_server_task(); // Enable websocket server - ESP_LOGI(TAG, "Connected to AP"); // ESP_LOGI(TAG, "Setup ws server"); // websocket_if_start(); @@ -2636,27 +2697,27 @@ void app_main(void) { set_time_from_sntp(); #endif +#if CONFIG_USE_DSP_PROCESSOR + dsp_processor_init(); +#endif + xTaskCreatePinnedToCore(&ota_server_task, "ota", 14 * 256, NULL, OTA_TASK_PRIORITY, t_ota_task, OTA_TASK_CORE_ID); - // xTaskCreatePinnedToCore (&http_get_task, "http", 10 * 256, NULL, - // HTTP_TASK_PRIORITY, &t_http_get_task, - // HTTP_TASK_CORE_ID); - xTaskCreatePinnedToCore(&http_get_task, "http", 3 * 1024, NULL, HTTP_TASK_PRIORITY, &t_http_get_task, HTTP_TASK_CORE_ID); - while (1) { - // audio_event_iface_msg_t msg; - vTaskDelay(portMAX_DELAY); //(pdMS_TO_TICKS(5000)); - - // ma120_read_error(0x20); - - esp_err_t ret = 0; // audio_event_iface_listen(evt, &msg, portMAX_DELAY); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret); - continue; - } - } + // while (1) { + // // audio_event_iface_msg_t msg; + // vTaskDelay(portMAX_DELAY); //(pdMS_TO_TICKS(5000)); + // + // // ma120_read_error(0x20); + // + // esp_err_t ret = 0; // audio_event_iface_listen(evt, &msg, + // portMAX_DELAY); if (ret != ESP_OK) { + // ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret); + // continue; + // } + // } } diff --git a/partitions.csv b/partitions.csv index d797921..ebe0e81 100644 --- a/partitions.csv +++ b/partitions.csv @@ -5,3 +5,4 @@ phy_init, data, phy, 0xf000, 4K, factory, app, factory, 0x10000, 1152K, ota_0, app, ota_0, 0x130000, 1152K, ota_1, app, ota_1, 0x250000, 1152K, +storage, data, spiffs, 0x370000, 576K, diff --git a/sdkconfig b/sdkconfig index 4345033..ccb8dee 100644 --- a/sdkconfig +++ b/sdkconfig @@ -143,7 +143,13 @@ CONFIG_ESP_LYRAT_V4_3_BOARD=y # # ESP32 DSP processor config # -# CONFIG_USE_DSP_PROCESSOR is not set +CONFIG_USE_DSP_PROCESSOR=y +# CONFIG_SNAPCLIENT_DSP_FLOW_STEREO is not set +# CONFIG_SNAPCLIENT_DSP_FLOW_BASSBOOST is not set +# CONFIG_SNAPCLIENT_DSP_FLOW_BIAMP is not set +CONFIG_SNAPCLIENT_DSP_FLOW_BASS_TREBLE_EQ=y +CONFIG_USE_BIQUAD_ASM=y +# CONFIG_SNAPCLIENT_USE_SOFT_VOL is not set # end of ESP32 DSP processor config # @@ -153,6 +159,24 @@ CONFIG_SNTP_TIMEZONE="UTC" CONFIG_SNTP_SERVER="pool.ntp.org" # end of SNTP Configuration +# +# Application Configuration +# + +# +# HTTP Server Setting +# +CONFIG_WEB_PORT=8000 +# end of HTTP Server Setting + +# +# GPIO Setting +# +CONFIG_GPIO_RANGE_MAX=33 +CONFIG_BLINK_GPIO=5 +# end of GPIO Setting +# end of Application Configuration + # # Wifi Configuration # @@ -457,7 +481,7 @@ CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y # # HTTP Server # -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -754,7 +778,7 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 # TCP # CONFIG_LWIP_MAX_ACTIVE_TCP=6 -CONFIG_LWIP_MAX_LISTENING_TCP=3 +CONFIG_LWIP_MAX_LISTENING_TCP=6 CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y CONFIG_LWIP_TCP_MAXRTX=12 CONFIG_LWIP_TCP_SYNMAXRTX=12 @@ -1120,7 +1144,18 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # # Virtual file system # -# CONFIG_VFS_SUPPORT_IO is not set +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_VFS_SUPPORT_TERMIOS=y + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +CONFIG_VFS_SEMIHOSTFS_HOST_PATH_MAX_LEN=128 +# end of Host File System I/O (Semihosting) # end of Virtual file system # @@ -1336,4 +1371,8 @@ CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y # CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set # CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +CONFIG_SEMIHOSTFS_HOST_PATH_MAX_LEN=128 # End of deprecated options diff --git a/sdkconfig.old b/sdkconfig.old index f71793e..7b2d5b3 100644 --- a/sdkconfig.old +++ b/sdkconfig.old @@ -143,13 +143,7 @@ CONFIG_ESP_LYRAT_V4_3_BOARD=y # # ESP32 DSP processor config # -CONFIG_USE_DSP_PROCESSOR=y -# CONFIG_SNAPCLIENT_DSP_FLOW_STEREO is not set -# CONFIG_SNAPCLIENT_DSP_FLOW_BASSBOOST is not set -# CONFIG_SNAPCLIENT_DSP_FLOW_BIAMP is not set -CONFIG_SNAPCLIENT_DSP_FLOW_BASS_TREBLE_EQ=y -CONFIG_USE_BIQUAD_ASM=y -# CONFIG_SNAPCLIENT_USE_SOFT_VOL is not set +# CONFIG_USE_DSP_PROCESSOR is not set # end of ESP32 DSP processor config # @@ -159,6 +153,24 @@ CONFIG_SNTP_TIMEZONE="UTC" CONFIG_SNTP_SERVER="pool.ntp.org" # end of SNTP Configuration +# +# Application Configuration +# + +# +# HTTP Server Setting +# +CONFIG_WEB_PORT=8000 +# end of HTTP Server Setting + +# +# GPIO Setting +# +CONFIG_GPIO_RANGE_MAX=33 +CONFIG_BLINK_GPIO=5 +# end of GPIO Setting +# end of Application Configuration + # # Wifi Configuration # @@ -463,7 +475,7 @@ CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y # # HTTP Server # -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -760,7 +772,7 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 # TCP # CONFIG_LWIP_MAX_ACTIVE_TCP=6 -CONFIG_LWIP_MAX_LISTENING_TCP=3 +CONFIG_LWIP_MAX_LISTENING_TCP=6 CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y CONFIG_LWIP_TCP_MAXRTX=12 CONFIG_LWIP_TCP_SYNMAXRTX=12 @@ -1126,7 +1138,18 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # # Virtual file system # -# CONFIG_VFS_SUPPORT_IO is not set +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_VFS_SUPPORT_TERMIOS=y + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +CONFIG_VFS_SEMIHOSTFS_HOST_PATH_MAX_LEN=128 +# end of Host File System I/O (Semihosting) # end of Virtual file system # @@ -1342,4 +1365,8 @@ CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y # CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set # CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +CONFIG_SEMIHOSTFS_HOST_PATH_MAX_LEN=128 # End of deprecated options