From 016a131f3385124ed73dedb849f7c7a01c3f3abc Mon Sep 17 00:00:00 2001 From: Karl Osterseher Date: Sun, 15 Jan 2023 20:21:49 +0100 Subject: [PATCH] add new component: user interface http server work in progress which eventually will enable the user to configure dsp processor on the fly using an on device http server. first try and possible fix for #22 Signed-off-by: Karl Osterseher --- components/dsp_processor/dsp_processor.c | 583 ++++++++++++------ .../dsp_processor/include/dsp_processor.h | 25 +- components/lightsnapcast/player.c | 29 +- components/ui_http_server/CMakeLists.txt | 9 + components/ui_http_server/Kconfig.projbuild | 32 + components/ui_http_server/html/ESP-LOGO.txt | 503 +++++++++++++++ components/ui_http_server/html/favicon.ico | Bin 0 -> 6429 bytes components/ui_http_server/html/index.html | 36 ++ components/ui_http_server/image/ESP-LOGO.png | Bin 0 -> 28639 bytes .../ui_http_server/include/ui_http_server.h | 11 + components/ui_http_server/ui_http_server.c | 421 +++++++++++++ main/main.c | 223 ++++--- partitions.csv | 1 + sdkconfig | 47 +- sdkconfig.old | 47 +- 15 files changed, 1681 insertions(+), 286 deletions(-) create mode 100644 components/ui_http_server/CMakeLists.txt create mode 100644 components/ui_http_server/Kconfig.projbuild create mode 100644 components/ui_http_server/html/ESP-LOGO.txt create mode 100644 components/ui_http_server/html/favicon.ico create mode 100644 components/ui_http_server/html/index.html create mode 100644 components/ui_http_server/image/ESP-LOGO.png create mode 100644 components/ui_http_server/include/ui_http_server.h create mode 100644 components/ui_http_server/ui_http_server.c 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 0000000000000000000000000000000000000000..17b8ff3cb6c3c920c732f563c2507c8472dfb95c GIT binary patch literal 6429 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 + + + + + + + +

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 0000000000000000000000000000000000000000..1bf46dd18324eb5a945937471eb829b2af1844cd GIT binary patch literal 28639 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 +#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