diff --git a/components/dsp_processor/Kconfig.projbuild b/components/dsp_processor/Kconfig.projbuild index ca351928..b273e68b 100644 --- a/components/dsp_processor/Kconfig.projbuild +++ b/components/dsp_processor/Kconfig.projbuild @@ -14,14 +14,6 @@ menu "ESP32 DSP processor config" help mix stereo audio (left and right channel) to mono and play it on the left AND the right channel - config USE_BIQUAD_ASM - bool "Use optimized asm version of Biquad_f32" - default y if IDF_TARGET_ESP32 - default n - depends on USE_DSP_PROCESSOR - help - Asm version 2 x speed on ESP32 - not working on ESP32-S2 - config SNAPCLIENT_USE_SOFT_VOL bool "Use software volume" default false diff --git a/components/dsp_processor/dsp_processor.c b/components/dsp_processor/dsp_processor.c index 6b17eb33..9874ca8f 100644 --- a/components/dsp_processor/dsp_processor.c +++ b/components/dsp_processor/dsp_processor.c @@ -34,12 +34,6 @@ typedef struct dsp_all_params_s { } flow_params[DSP_FLOW_COUNT]; } dsp_all_params_t; -#ifdef CONFIG_USE_BIQUAD_ASM -#define BIQUAD dsps_biquad_f32_ae32 -#else -#define BIQUAD dsps_biquad_f32 -#endif - static const char *TAG = "dsp_proc"; #define DSP_PROCESSOR_LEN 16 @@ -521,9 +515,9 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { } // BASS - BIQUAD(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); + dsps_biquad_f32(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); // TREBLE - BIQUAD(sbufout0, sbuffer0, max, filter[1].coeffs, filter[1].w); + dsps_biquad_f32(sbufout0, sbuffer0, max, filter[1].coeffs, filter[1].w); for (i = 0; i < max; i++) { valint = float_to_int16_clamped(sbuffer0[i]); @@ -539,9 +533,9 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { } // BASS - BIQUAD(sbuffer0, sbufout0, max, filter[2].coeffs, filter[2].w); + dsps_biquad_f32(sbuffer0, sbufout0, max, filter[2].coeffs, filter[2].w); // TREBLE - BIQUAD(sbufout0, sbuffer0, max, filter[3].coeffs, filter[3].w); + dsps_biquad_f32(sbufout0, sbuffer0, max, filter[3].coeffs, filter[3].w); for (i = 0; i < max; i++) { valint = float_to_int16_clamped(sbuffer0[i]); @@ -597,7 +591,7 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { dynamic_vol * ((float)((int16_t)(tmp[i] & 0xFFFF))) / INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); + dsps_biquad_f32(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); for (i = 0; i < max; i++) { valint = float_to_int16_clamped(sbufout0[i]); @@ -610,7 +604,7 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { ((float)((int16_t)((tmp[i] & 0xFFFF0000) >> 16))) / INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, max, filter[1].coeffs, filter[1].w); + dsps_biquad_f32(sbuffer0, sbufout0, max, filter[1].coeffs, filter[1].w); for (i = 0; i < max; i++) { valint = float_to_int16_clamped(sbufout0[i]); @@ -636,8 +630,8 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { sbuffer0[i] = dynamic_vol * ((float)((int16_t)(tmp[i] & 0xFFFF))) / INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); - BIQUAD(sbufout0, sbuffer0, max, filter[1].coeffs, filter[1].w); + dsps_biquad_f32(sbuffer0, sbufout0, max, filter[0].coeffs, filter[0].w); + dsps_biquad_f32(sbufout0, sbuffer0, max, filter[1].coeffs, filter[1].w); for (i = 0; i < max; i++) { valint = float_to_int16_clamped(sbuffer0[i]); @@ -650,8 +644,8 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { ((float)((int16_t)((tmp[i] & 0xFFFF0000) >> 16))) / INT16_MAX; } - BIQUAD(sbuffer0, sbufout0, max, filter[2].coeffs, filter[2].w); - BIQUAD(sbufout0, sbuffer0, max, filter[3].coeffs, filter[3].w); + dsps_biquad_f32(sbuffer0, sbufout0, max, filter[2].coeffs, filter[2].w); + dsps_biquad_f32(sbufout0, sbuffer0, max, filter[3].coeffs, filter[3].w); for (i = 0; i < max; i++) { valint = float_to_int16_clamped(sbuffer0[i]); @@ -664,16 +658,16 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { case dspf2DOT1: { // Process audio L + R LOW PASS FILTER /* - BIQUAD(sbuffer2, sbuftmp0, len, bq[0].coeffs, bq[0].w); - BIQUAD(sbuftmp0, sbufout2, len, bq[1].coeffs, bq[1].w); + dsps_biquad_f32(sbuffer2, sbuftmp0, len, bq[0].coeffs, bq[0].w); + dsps_biquad_f32(sbuftmp0, sbufout2, len, bq[1].coeffs, bq[1].w); // Process audio L HIGH PASS FILTER - BIQUAD(sbuffer0, sbuftmp0, len, bq[2].coeffs, bq[2].w); - BIQUAD(sbuftmp0, sbufout0, len, bq[3].coeffs, bq[3].w); + dsps_biquad_f32(sbuffer0, sbuftmp0, len, bq[2].coeffs, bq[2].w); + dsps_biquad_f32(sbuftmp0, sbufout0, len, bq[3].coeffs, bq[3].w); // Process audio R HIGH PASS FILTER - BIQUAD(sbuffer1, sbuftmp0, len, bq[4].coeffs, bq[4].w); - BIQUAD(sbuftmp0, sbufout1, len, bq[5].coeffs, bq[5].w); + dsps_biquad_f32(sbuffer1, sbuftmp0, len, bq[4].coeffs, bq[4].w); + dsps_biquad_f32(sbuftmp0, sbufout1, len, bq[5].coeffs, bq[5].w); int16_t valint[5]; for (uint16_t i = 0; i < len; i++) { @@ -703,16 +697,16 @@ int dsp_processor_worker(void *p_pcmChnk, const void *p_scSet) { case dspfFunkyHonda: { // Process audio L + R LOW PASS FILTER /* - BIQUAD(sbuffer2, sbuftmp0, len, bq[0].coeffs, bq[0].w); - BIQUAD(sbuftmp0, sbufout2, len, bq[1].coeffs, bq[1].w); + dsps_biquad_f32(sbuffer2, sbuftmp0, len, bq[0].coeffs, bq[0].w); + dsps_biquad_f32(sbuftmp0, sbufout2, len, bq[1].coeffs, bq[1].w); // Process audio L HIGH PASS FILTER - BIQUAD(sbuffer0, sbuftmp0, len, bq[2].coeffs, bq[2].w); - BIQUAD(sbuftmp0, sbufout0, len, bq[3].coeffs, bq[3].w); + dsps_biquad_f32(sbuffer0, sbuftmp0, len, bq[2].coeffs, bq[2].w); + dsps_biquad_f32(sbuftmp0, sbufout0, len, bq[3].coeffs, bq[3].w); // Process audio R HIGH PASS FILTER - BIQUAD(sbuffer1, sbuftmp0, len, bq[4].coeffs, bq[4].w); - BIQUAD(sbuftmp0, sbufout1, len, bq[5].coeffs, bq[5].w); + dsps_biquad_f32(sbuffer1, sbuftmp0, len, bq[4].coeffs, bq[4].w); + dsps_biquad_f32(sbuftmp0, sbufout1, len, bq[5].coeffs, bq[5].w); uint16_t scale = 16384; // INT16_MAX int16_t valint[5]; diff --git a/components/lightsnapcast/include/player.h b/components/lightsnapcast/include/player.h index dcd69aee..3e598319 100644 --- a/components/lightsnapcast/include/player.h +++ b/components/lightsnapcast/include/player.h @@ -62,15 +62,20 @@ typedef struct snapcastSetting_s { i2s_data_bit_width_t bits; bool muted; - uint32_t volume; char *pcmBuf; uint32_t pcmBufSize; } snapcastSetting_t; -int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_); +typedef enum { IDLE = 0, PLAYING, PAUSED } player_state_e; +int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_, void (*set_mute_cb)(bool)); int deinit_player(void); int start_player(snapcastSetting_t *setting); +void pause_player(bool pause); + +void call_state_cb(void); +void add_player_state_cb(void (*cb)(void)); +player_state_e get_player_state(void); int32_t allocate_pcm_chunk_memory(pcm_chunk_message_t **pcmChunk, size_t bytes); int32_t insert_pcm_chunk(pcm_chunk_message_t *pcmChunk); diff --git a/components/lightsnapcast/include/snapcast_protocol_parser.h b/components/lightsnapcast/include/snapcast_protocol_parser.h index b8b38ba2..791dc956 100644 --- a/components/lightsnapcast/include/snapcast_protocol_parser.h +++ b/components/lightsnapcast/include/snapcast_protocol_parser.h @@ -1,16 +1,15 @@ #ifndef __SNAPCAST_PROTOCOL_PARSER_H__ #define __SNAPCAST_PROTOCOL_PARSER_H__ +#include "player.h" // needed for coded_type_t #include "snapcast.h" -#include "player.h" // needed for coded_type_t - typedef struct decoderData_s { uint32_t type; // should be SNAPCAST_MESSAGE_CODEC_HEADER // or SNAPCAST_MESSAGE_WIRE_CHUNK - uint8_t *inData; + uint8_t* inData; tv_t timestamp; - uint8_t *outData; + uint8_t* outData; uint32_t bytes; } decoderData_t; @@ -22,27 +21,21 @@ typedef struct { } snapcast_protocol_parser_t; typedef enum { - PARSER_COMPLETE = 0, - PARSER_INCOMPLETE, - PARSER_CRITICAL_ERROR, - PARSER_CONNECTION_ERROR, + PARSER_OK = 0, + PARSER_RESTART_CONNECTION, } parser_return_state_t; parser_return_state_t parse_base_message(snapcast_protocol_parser_t* parser, base_message_t* base_message_rx); -parser_return_state_t parse_wire_chunk_message(snapcast_protocol_parser_t* parser, - base_message_t* base_message_rx, - bool received_codec_header, - codec_type_t codec, - pcm_chunk_message_t** pcmData, - wire_chunk_message_t* wire_chnk, - decoderData_t* decoderChunk); +parser_return_state_t parse_wire_chunk_message( + snapcast_protocol_parser_t* parser, base_message_t* base_message_rx, + codec_type_t codec, pcm_chunk_message_t** pcmData, + wire_chunk_message_t* wire_chnk, decoderData_t* decoderChunk); parser_return_state_t parse_codec_header_message( - snapcast_protocol_parser_t* parser, - bool* received_codec_header, codec_type_t* codec, - char** codecPayload, uint32_t* codecPayloadLen); + snapcast_protocol_parser_t* parser, bool* received_codec_header, + codec_type_t* codec, char** codecPayload, uint32_t* codecPayloadLen); parser_return_state_t parse_sever_settings_message( snapcast_protocol_parser_t* parser, base_message_t* base_message_rx, @@ -52,7 +45,7 @@ parser_return_state_t parse_time_message(snapcast_protocol_parser_t* parser, base_message_t* base_message_rx, time_message_t* time_message_rx); -parser_return_state_t parse_unknown_message(snapcast_protocol_parser_t* parser, - base_message_t* base_message_rx); +parser_return_state_t parser_skip_typed_message( + snapcast_protocol_parser_t* parser, base_message_t* base_message_rx); #endif // __SNAPCAST_PROTOCOL_PARSER_H__ diff --git a/components/lightsnapcast/player.c b/components/lightsnapcast/player.c index 209de796..f6ab9f97 100644 --- a/components/lightsnapcast/player.c +++ b/components/lightsnapcast/player.c @@ -17,7 +17,9 @@ #include "esp_log.h" #include "esp_timer.h" +#if (CONFIG_ESP_WIFI_ENABLED || CONFIG_ESP_HOST_WIFI_ENABLED) #include "esp_wifi.h" +#endif #include "soc/rtc.h" #if SOC_I2S_SUPPORTS_APLL @@ -117,11 +119,20 @@ static bool gpTimerRunning = false; static void player_task(void *pvParameters); +//player state bool gotSettings = false; -bool playerstarted = false; +bool playerStarted = false; +bool playerPaused = false; +static SemaphoreHandle_t playerStateMux = NULL; + +typedef struct state_cb_s { + void (*cb)(void); + struct state_cb_s *next; +} state_cb_t; -extern void audio_set_mute(bool mute); -extern void audio_dac_enable(bool enabled); +static state_cb_t *state_cb_head = NULL; + +static void (*audio_set_mute)(bool mute); static i2s_chan_handle_t tx_chan = NULL; // I2S tx channel handler static bool i2sEnabled = false; @@ -129,10 +140,6 @@ static bool i2sEnabled = false; i2s_std_gpio_config_t pin_config0; i2s_port_t i2sNum; -// Function to find the minimum using ternary operator -static int64_t MIN(int64_t x, int64_t y) { - return (x < y) ? x : y; -} /** * @@ -390,17 +397,22 @@ int deinit_player(void) { // must disable i2s before stopping player task or it will hang my_i2s_channel_disable(tx_chan); - + + //don't take playerStateMux here, otherwise we might block player task from stopping + //if (playerStateMux != NULL) { + // xSemaphoreTake(playerStateMux, pdMS_TO_TICKS(10000)); + //} + //wait max 10s for task to stop itself for(int i = 0; i< 100; i++) { - if (playerstarted) { + if (playerStarted) { vTaskDelay(pdMS_TO_TICKS(100)); } else { break; } } - // stop the task f still running + // stop the task if still running if (playerTaskHandle != NULL) { vTaskDelete(playerTaskHandle); playerTaskHandle = NULL; @@ -411,6 +423,10 @@ int deinit_player(void) { tx_chan = NULL; } + if (playerStateMux != NULL) { + vSemaphoreDelete(playerStateMux); + playerStateMux = NULL; + } if (snapcastSettingsMux != NULL) { vSemaphoreDelete(snapcastSettingsMux); snapcastSettingsMux = NULL; @@ -445,8 +461,13 @@ int deinit_player(void) { /** * call before http task creation! */ -int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_) { +int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_, void (*set_mute_cb)(bool)) { int ret = 0; + if (set_mute_cb == NULL) { + ESP_LOGE(TAG, "set_mute_cb is NULL"); + return -1; + } + audio_set_mute = set_mute_cb; deinit_player(); @@ -459,14 +480,17 @@ int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_) { currentSnapcastSetting.sr = 0; currentSnapcastSetting.ch = 0; currentSnapcastSetting.bits = 0; - currentSnapcastSetting.muted = true; - currentSnapcastSetting.volume = 0; if (snapcastSettingsMux == NULL) { snapcastSettingsMux = xSemaphoreCreateMutex(); xSemaphoreGive(snapcastSettingsMux); } + if (playerStateMux == NULL) { + playerStateMux = xSemaphoreCreateMutex(); + xSemaphoreGive(playerStateMux); + } + /** ret = player_setup_i2s(¤tSnapcastSetting); if (ret < 0) { @@ -488,7 +512,7 @@ int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_) { #if USE_TIMEFILTER // init Kalmann time filter - TIMEFILTER_Init(&latencyTimeFilter, 0.01, 0.0, 1.001, 0.75, 100); + TIMEFILTER_Init(&latencyTimeFilter, 0.01, 0.0, 1.001, 0.75, 100, 2.0); #else // init diff buff median filter latencyMedianFilter.numNodes = LATENCY_MEDIAN_FILTER_LEN; @@ -516,18 +540,25 @@ int init_player(i2s_std_gpio_config_t pin_config0_, i2s_port_t i2sNum_) { * call to start the player task */ int start_player(snapcastSetting_t *setting) { - if (playerstarted){ - return -1; - } - playerstarted = true; + if (xSemaphoreTake(playerStateMux, 0) != pdTRUE) { + // shutdown in progress, don't start player + return -1; + } + if (playerStarted || playerPaused){ + xSemaphoreGive(playerStateMux); + return -1; + } + playerStarted = true; int ret = 0; ret = player_setup_i2s(setting); if (ret < 0) { ESP_LOGE(TAG, "player_setup_i2s failed: %d", ret); - playerstarted = false; + playerStarted = false; + xSemaphoreGive(playerStateMux); return -1; } + xSemaphoreGive(playerStateMux); tg0_timer_init(); @@ -574,12 +605,65 @@ int start_player(snapcastSetting_t *setting) { SYNC_TASK_PRIORITY, &playerTaskHandle, SYNC_TASK_CORE_ID); - + call_state_cb(); ESP_LOGI(TAG, "start player done"); return 0; } +void pause_player(bool pause) { + xSemaphoreTake(playerStateMux, portMAX_DELAY); + if (pause != playerPaused) { + playerPaused = pause; + xSemaphoreGive(playerStateMux); + if (pause && playerTaskHandle != NULL) { + xTaskNotifyGiveIndexed(playerTaskHandle, 1); + } + else { + call_state_cb(); // notify state change, e.g. for http task to send pcm + } + } else { + xSemaphoreGive(playerStateMux); + } +} + +player_state_e get_player_state(void) { + xSemaphoreTake(playerStateMux, portMAX_DELAY); + player_state_e state = IDLE; + if (playerPaused) { + state = PAUSED; + } else if (playerStarted) { + state = PLAYING; + } + xSemaphoreGive(playerStateMux); + return state; +} + +void call_state_cb(void) { + state_cb_t *current = state_cb_head; + while (current != NULL) { + if (current->cb != NULL) { + current->cb(); + } + current = current->next; + } +} + +/** + * add callback to be called when player state changes, e.g. from not started to started. + * Callbacks needs to be implemented thread safe as they will be called from player task + */ +void add_player_state_cb(void (*cb)()) { + state_cb_t *new_cb = malloc(sizeof(state_cb_t)); + if (new_cb == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for state callback"); + return; + } + new_cb->cb = cb; + new_cb->next = state_cb_head; + state_cb_head = new_cb; +} + /** * */ @@ -618,7 +702,7 @@ int32_t player_latency_insert(int64_t newValue, int64_t max_error, int64_t time_ TIMEFILTER_Insert(&latencyTimeFilter, newValue, max_error, time_added); int64_t last_update_ = latencyTimeFilter.last_update_; double offset_ = latencyTimeFilter.offset_; - double drift_ = latencyTimeFilter.drift_; + double drift_ = latencyTimeFilter.use_drift_ ? latencyTimeFilter.drift_ : 0.0; if (xSemaphoreTake(latencyBufSemaphoreHandle, pdMS_TO_TICKS(0)) == pdTRUE) { if (TIMEFILTER_isFull(&latencyTimeFilter, LATENCY_TIME_FILTER_FULL)) { xSemaphoreGive(latencyBufFullSemaphoreHandle); @@ -681,8 +765,7 @@ int32_t player_send_snapcast_setting(snapcastSetting_t *setting) { if ((curSet.bits != setting->bits) || (curSet.buf_ms != setting->buf_ms) || (curSet.ch != setting->ch) || (curSet.chkInFrames != setting->chkInFrames) || - (curSet.codec != setting->codec) || (curSet.muted != setting->muted) || - (curSet.sr != setting->sr) || (curSet.volume != setting->volume) || + (curSet.codec != setting->codec) || (curSet.sr != setting->sr) || (curSet.cDacLat_ms != setting->cDacLat_ms)) { ret = player_set_snapcast_settings(setting); if (ret != pdPASS) { @@ -691,16 +774,7 @@ int32_t player_send_snapcast_setting(snapcastSetting_t *setting) { "snapcast setting"); } - // check if it is only volume / mute related setting, which is handled by - // http_get_task() - // if ((((curSet.muted != setting->muted) || - //(curSet.volume != setting->volume)) && - //((curSet.bits == setting->bits) && - //(curSet.buf_ms == setting->buf_ms) && (curSet.ch == setting->ch) && - //(curSet.chkInFrames == setting->chkInFrames) && - //(curSet.codec == setting->codec) && (curSet.sr == setting->sr) && - //(curSet.cDacLat_ms == setting->cDacLat_ms))) == false) { - // notify needed + // notify needed if ((playerTaskHandle != NULL) && (snapcastSettingQueueHandle != NULL)) { ret = xQueueOverwrite(snapcastSettingQueueHandle, &settingChanged); if (ret != pdPASS) { @@ -1354,14 +1428,18 @@ int32_t insert_pcm_chunk(pcm_chunk_message_t *pcmChunk) { return -1; } + if (playerPaused) { + free_pcm_chunk(pcmChunk); + return 0; + } if (pcmChkQHdl == NULL) { - ESP_LOGW(TAG, "pcm chunk queue not created. Player started: %s", playerstarted ? "True": "False"); + ESP_LOGW(TAG, "pcm chunk queue not created. Player started: %s", playerStarted ? "True": "False"); free_pcm_chunk(pcmChunk); snapcastSetting_t curSet; player_get_snapcast_settings(&curSet); - if (!curSet.muted && gotSettings) { + if (gotSettings) { start_player(&curSet); } @@ -1483,7 +1561,7 @@ static void player_task(void *pvParameters) { // // ESP_LOGI(TAG, "created new queue with %d", entries); // } - audio_set_mute(scSet.muted); + audio_set_mute(false); // wait for early time syncs to be ready xSemaphoreTake(latencyBufFullSemaphoreHandle, portMAX_DELAY); @@ -1564,13 +1642,11 @@ static void player_task(void *pvParameters) { (scSet.ch != __scSet.ch) || (scSet.buf_ms != __scSet.buf_ms)) { ESP_LOGI(TAG, "snapserver config changed, buffer %ldms, chunk %ld frames, " - "sample rate %ld, ch %d, bits %d mute %d latency %ld", + "sample rate %ld, ch %d, bits %d latency %ld", __scSet.buf_ms, __scSet.chkInFrames, __scSet.sr, __scSet.ch, - __scSet.bits, __scSet.muted, __scSet.cDacLat_ms); - } else { - audio_set_mute(__scSet.muted); - ESP_LOGI(TAG, "snapserver config changed, mute: %d", __scSet.muted); + __scSet.bits, __scSet.cDacLat_ms); } + audio_set_mute(false); scSet = __scSet; // store for next round @@ -1702,8 +1778,6 @@ static void player_task(void *pvParameters) { my_gptimer_stop(gptimer); - audio_dac_enable(true); - my_i2s_channel_enable(tx_chan); playback_start_time_us = esp_timer_get_time(); @@ -1719,7 +1793,7 @@ static void player_task(void *pvParameters) { // TODO: use a timer to un-mute non blocking vTaskDelay(pdMS_TO_TICKS(2)); - audio_set_mute(scSet.muted); + audio_set_mute(false); ESP_LOGI(TAG, "initial sync age: %lldus, chunk duration: %lldus", age, chunkDuration_us); @@ -1733,18 +1807,28 @@ static void player_task(void *pvParameters) { chnk = NULL; } - wifi_ap_record_t ap; - esp_wifi_sta_get_ap_info(&ap); my_gptimer_stop(gptimer); int msgWaiting = uxQueueMessagesWaiting(pcmChkQHdl); +#if (CONFIG_ESP_WIFI_ENABLED || CONFIG_ESP_HOST_WIFI_ENABLED) + wifi_ap_record_t ap; + esp_wifi_sta_get_ap_info(&ap); ESP_LOGW(TAG, "RESYNCING HARD 1: age %lldus, latency %lldus, free %d, " "largest block %d, rssi: %d, left in queue %d", age, diff2Server, heap_caps_get_free_size(MALLOC_CAP_32BIT), - heap_caps_get_largest_free_block(MALLOC_CAP_32BIT), ap.rssi, msgWaiting); + heap_caps_get_largest_free_block(MALLOC_CAP_32BIT), + ap.rssi, msgWaiting); +#else + ESP_LOGW(TAG, + "RESYNCING HARD 2: age %lldus, latency %lldus, free " + "%d, largest block %d, %d", age, diff2Server, + heap_caps_get_free_size(MALLOC_CAP_32BIT), + heap_caps_get_largest_free_block(MALLOC_CAP_32BIT), + msgWaiting); +#endif // get count of chunks we are late for uint32_t c = ceil((float)age / (float)chunkDuration_us); // round up @@ -2007,6 +2091,7 @@ static void player_task(void *pvParameters) { chnk = NULL; } +#if (CONFIG_ESP_WIFI_ENABLED || CONFIG_ESP_HOST_WIFI_ENABLED) wifi_ap_record_t ap; esp_wifi_sta_get_ap_info(&ap); @@ -2016,7 +2101,15 @@ static void player_task(void *pvParameters) { age, diff2Server, heap_caps_get_free_size(MALLOC_CAP_32BIT), heap_caps_get_largest_free_block(MALLOC_CAP_32BIT), - msgWaiting, ap.rssi); + msgWaiting,ap.rssi); +#else + ESP_LOGW(TAG, + "RESYNCING HARD 2: age %lldus, latency %lldus, free " + "%d, largest block %d, %d", age, diff2Server, + heap_caps_get_free_size(MALLOC_CAP_32BIT), + heap_caps_get_largest_free_block(MALLOC_CAP_32BIT), + msgWaiting); +#endif my_gptimer_stop(gptimer); @@ -2111,16 +2204,22 @@ static void player_task(void *pvParameters) { initialSync = 0; audio_set_mute(true); - audio_dac_enable(false); my_i2s_channel_disable(tx_chan); i2s_del_channel(tx_chan); tx_chan = NULL; break; } + if (ulTaskNotifyTakeIndexed(1, pdTRUE, 0) == pdTRUE) { + audio_set_mute(true); + my_i2s_channel_disable(tx_chan); + i2s_del_channel(tx_chan); + tx_chan = NULL; + break; + } } ret = 0; - + xSemaphoreTake(playerStateMux, portMAX_DELAY); xSemaphoreTake(snapcastSettingsMux, portMAX_DELAY); // delete the queue vQueueDelete(snapcastSettingQueueHandle); @@ -2134,8 +2233,10 @@ static void player_task(void *pvParameters) { ret = destroy_pcm_queue(&pcmChkQHdl); tg0_timer_deinit(); - playerstarted = false; + playerStarted = false; + call_state_cb(); ESP_LOGI(TAG, "stop player done"); playerTaskHandle = NULL; + xSemaphoreGive(playerStateMux); vTaskDelete(NULL); } diff --git a/components/lightsnapcast/snapcast_protocol_parser.c b/components/lightsnapcast/snapcast_protocol_parser.c index 886d48bc..83e6eb5a 100644 --- a/components/lightsnapcast/snapcast_protocol_parser.c +++ b/components/lightsnapcast/snapcast_protocol_parser.c @@ -3,61 +3,67 @@ #include "esp_log.h" // MACROS for reading from connection -#define READ_BYTE(parser, dest) \ - do { \ - char _byte; \ - if ((parser)->get_byte_function((parser)->get_byte_context, &_byte) != 0) { \ - return PARSER_CONNECTION_ERROR; \ - } \ - (dest) = _byte; \ - } while(0) - -#define READ_UINT16_LE(parser, dest) \ - do { \ - char _bytes[2]; \ - if ((parser)->get_byte_function((parser)->get_byte_context, &_bytes[0]) != 0 || \ - (parser)->get_byte_function((parser)->get_byte_context, &_bytes[1]) != 0) { \ - return PARSER_CONNECTION_ERROR; \ - } \ - (dest) = (_bytes[0] & 0xFF) | ((_bytes[1] & 0xFF) << 8); \ - } while(0) - -#define READ_UINT32_LE(parser, dest) \ - do { \ - char _bytes[4]; \ - for (int _i = 0; _i < 4; _i++) { \ - if ((parser)->get_byte_function((parser)->get_byte_context, &_bytes[_i]) != 0) { \ - return PARSER_CONNECTION_ERROR; \ - } \ - } \ - (dest) = (_bytes[0] & 0xFF) | ((_bytes[1] & 0xFF) << 8) | \ +#define READ_BYTE(parser, dest) \ + do { \ + char _byte; \ + if ((parser)->get_byte_function((parser)->get_byte_context, &_byte) != \ + 0) { \ + return PARSER_RESTART_CONNECTION; \ + } \ + (dest) = _byte; \ + } while (0) + +#define READ_UINT16_LE(parser, dest) \ + do { \ + char _bytes[2]; \ + if ((parser)->get_byte_function((parser)->get_byte_context, &_bytes[0]) != \ + 0 || \ + (parser)->get_byte_function((parser)->get_byte_context, &_bytes[1]) != \ + 0) { \ + return PARSER_RESTART_CONNECTION; \ + } \ + (dest) = (_bytes[0] & 0xFF) | ((_bytes[1] & 0xFF) << 8); \ + } while (0) + +#define READ_UINT32_LE(parser, dest) \ + do { \ + char _bytes[4]; \ + for (int _i = 0; _i < 4; _i++) { \ + if ((parser)->get_byte_function((parser)->get_byte_context, \ + &_bytes[_i]) != 0) { \ + return PARSER_RESTART_CONNECTION; \ + } \ + } \ + (dest) = (_bytes[0] & 0xFF) | ((_bytes[1] & 0xFF) << 8) | \ ((_bytes[2] & 0xFF) << 16) | ((_bytes[3] & 0xFF) << 24); \ - } while(0) + } while (0) -#define READ_TIMESTAMP(parser, ts) \ - do { \ - READ_UINT32_LE(parser, (ts).sec); \ +#define READ_TIMESTAMP(parser, ts) \ + do { \ + READ_UINT32_LE(parser, (ts).sec); \ READ_UINT32_LE(parser, (ts).usec); \ - } while(0) - -#define READ_DATA(parser, dest, len) \ - do { \ - for (uint32_t _i = 0; _i < (len); _i++) { \ - if ((parser)->get_byte_function((parser)->get_byte_context, &(dest)[_i]) != 0) { \ - return PARSER_CONNECTION_ERROR; \ - } \ - } \ - } while(0) - -#define READ_DATA_WITH_CLEANUP(parser, dest, len, cleanup) \ - do { \ - for (uint32_t _i = 0; _i < (len); _i++) { \ - if ((parser)->get_byte_function((parser)->get_byte_context, &(dest)[_i]) != 0) { \ - cleanup; \ - return PARSER_CONNECTION_ERROR; \ - } \ - } \ - } while(0) + } while (0) + +#define READ_DATA(parser, dest, len) \ + do { \ + for (uint32_t _i = 0; _i < (len); _i++) { \ + if ((parser)->get_byte_function((parser)->get_byte_context, \ + &(dest)[_i]) != 0) { \ + return PARSER_RESTART_CONNECTION; \ + } \ + } \ + } while (0) + +#define READ_DATA_WITH_CLEANUP(parser, dest, len, cleanup) \ + do { \ + for (uint32_t _i = 0; _i < (len); _i++) { \ + if ((parser)->get_byte_function((parser)->get_byte_context, \ + &(dest)[_i]) != 0) { \ + cleanup; \ + return PARSER_RESTART_CONNECTION; \ + } \ + } \ + } while (0) static const char* TAG = "SNAPCAST_PROTOCOL_PARSER"; @@ -70,19 +76,13 @@ parser_return_state_t parse_base_message(snapcast_protocol_parser_t* parser, READ_TIMESTAMP(parser, base_message_rx->received); READ_UINT32_LE(parser, base_message_rx->size); - return PARSER_COMPLETE; + return PARSER_OK; } - - -parser_return_state_t parse_wire_chunk_message(snapcast_protocol_parser_t* parser, - base_message_t* base_message_rx, - bool received_codec_header, - codec_type_t codec, - pcm_chunk_message_t** pcmData, - wire_chunk_message_t* wire_chnk, - decoderData_t* decoderChunk) { - +parser_return_state_t parse_wire_chunk_message( + snapcast_protocol_parser_t* parser, base_message_t* base_message_rx, + codec_type_t codec, pcm_chunk_message_t** pcmData, + wire_chunk_message_t* wire_chnk, decoderData_t* decoderChunk) { READ_TIMESTAMP(parser, wire_chnk->timestamp); READ_UINT32_LE(parser, wire_chnk->size); @@ -104,108 +104,109 @@ parser_return_state_t parse_wire_chunk_message(snapcast_protocol_parser_t* parse int32_t payloadDataShift = 0; size_t tmp_size = base_message_rx->size - 12; - if (received_codec_header == true) { - switch (codec) { - case OPUS: - case FLAC: { - READ_DATA(parser, (char*)decoderChunk->inData, tmp_size); - payloadOffset += tmp_size; - decoderChunk->outData = NULL; - decoderChunk->type = SNAPCAST_MESSAGE_WIRE_CHUNK; + // if (received_codec_header == true) { //already checked in caller, so should + // always be true + switch (codec) { + case OPUS: + case FLAC: { + READ_DATA(parser, (char*)decoderChunk->inData, tmp_size); + payloadOffset += tmp_size; + decoderChunk->outData = NULL; + decoderChunk->type = SNAPCAST_MESSAGE_WIRE_CHUNK; + + break; + } - break; - } + case PCM: { + size_t _tmp = tmp_size; - case PCM: { - size_t _tmp = tmp_size; + if (*pcmData == NULL) { + if (allocate_pcm_chunk_memory(pcmData, wire_chnk->size) < 0) { + *pcmData = NULL; + } - if (*pcmData == NULL) { - if (allocate_pcm_chunk_memory(pcmData, wire_chnk->size) < 0) { - *pcmData = NULL; - } + tmpData = 0; + payloadDataShift = 3; + payloadOffset = 0; + } - tmpData = 0; + while (_tmp--) { + char tmp_val; + READ_BYTE(parser, tmp_val); + tmpData |= ((uint32_t)tmp_val << (8 * payloadDataShift)); + + payloadDataShift--; + if (payloadDataShift < 0) { payloadDataShift = 3; - payloadOffset = 0; - } - while (_tmp--) { - char tmp_val; - READ_BYTE(parser, tmp_val); - tmpData |= ((uint32_t)tmp_val << (8 * payloadDataShift)); - - payloadDataShift--; - if (payloadDataShift < 0) { - payloadDataShift = 3; - - if ((*pcmData) && ((*pcmData)->fragment->payload)) { - volatile uint32_t* sample; - uint8_t dummy1; - uint32_t dummy2 = 0; - - // TODO: find a more - // clever way to do this, - // best would be to - // actually store it the - // right way in the first - // place - dummy1 = tmpData >> 24; - dummy2 |= (uint32_t)dummy1 << 16; - dummy1 = tmpData >> 16; - dummy2 |= (uint32_t)dummy1 << 24; - dummy1 = tmpData >> 8; - dummy2 |= (uint32_t)dummy1 << 0; - dummy1 = tmpData >> 0; - dummy2 |= (uint32_t)dummy1 << 8; - tmpData = dummy2; - - sample = (volatile uint32_t *)(&((*pcmData)->fragment->payload[payloadOffset])); - *sample = (volatile uint32_t)tmpData; - - payloadOffset += 4; - } - - tmpData = 0; + if ((*pcmData) && ((*pcmData)->fragment->payload)) { + volatile uint32_t* sample; + uint8_t dummy1; + uint32_t dummy2 = 0; + + // TODO: find a more + // clever way to do this, + // best would be to + // actually store it the + // right way in the first + // place + dummy1 = tmpData >> 24; + dummy2 |= (uint32_t)dummy1 << 16; + dummy1 = tmpData >> 16; + dummy2 |= (uint32_t)dummy1 << 24; + dummy1 = tmpData >> 8; + dummy2 |= (uint32_t)dummy1 << 0; + dummy1 = tmpData >> 0; + dummy2 |= (uint32_t)dummy1 << 8; + tmpData = dummy2; + + sample = (volatile uint32_t*)(&( + (*pcmData)->fragment->payload[payloadOffset])); + *sample = (volatile uint32_t)tmpData; + + payloadOffset += 4; } - } - break; - } - default: { - ESP_LOGE(TAG, "Decoder (1) not supported"); - return PARSER_CRITICAL_ERROR; + tmpData = 0; + } } + + break; + } + default: { + ESP_LOGE(TAG, "Decoder (1) not supported. This should never happen!"); + // The case NONE should never happen, because we only set + // received_codec_header to true, if we got a supported codec header + // message (cf. parse_codec_header_message). So if we get here, something + // went very wrong. critical error + esp_restart(); } } - if (received_codec_header == true) { - return PARSER_COMPLETE; - } else { - return PARSER_INCOMPLETE; // TODO: right return value? - } + return PARSER_OK; } parser_return_state_t parse_codec_header_message( - snapcast_protocol_parser_t* parser, - bool* received_codec_header, codec_type_t* codec, - char** codecPayload, uint32_t* codecPayloadLen) { + snapcast_protocol_parser_t* parser, bool* received_codec_header, + codec_type_t* codec, char** codecPayload, uint32_t* codecPayloadLen) { *received_codec_header = false; uint32_t codecStringLen = 0; READ_UINT32_LE(parser, codecStringLen); - char codecString[8]; // longest supported string has 4 + 1 chars + char codecString[8]; // longest supported string has 4 + 1 chars if (codecStringLen + 1 > sizeof(codecString)) { - READ_DATA(parser, codecString, sizeof(codecString)-1); - codecString[sizeof(codecString)-1] = 0; // null terminate + READ_DATA(parser, codecString, sizeof(codecString) - 1); + codecString[sizeof(codecString) - 1] = 0; // null terminate ESP_LOGE(TAG, "Codec : %s... not supported %lu", codecStringLen); ESP_LOGI(TAG, "Change encoder codec to " "opus, flac or pcm in " "/etc/snapserver.conf on " "server"); - return PARSER_CRITICAL_ERROR; + // restart connection + return PARSER_RESTART_CONNECTION; } READ_DATA(parser, codecString, codecStringLen); @@ -230,7 +231,8 @@ parser_return_state_t parse_codec_header_message( "/etc/snapserver.conf on " "server"); - return PARSER_CRITICAL_ERROR; + // restart connection + return PARSER_RESTART_CONNECTION; } // @@ -243,14 +245,15 @@ parser_return_state_t parse_codec_header_message( "couldn't get memory " "for codec payload"); - return PARSER_CRITICAL_ERROR; + // critical error + esp_restart(); } READ_DATA(parser, *codecPayload, *codecPayloadLen); *received_codec_header = true; - return PARSER_COMPLETE; + return PARSER_OK; } parser_return_state_t parse_sever_settings_message( @@ -268,14 +271,16 @@ parser_return_state_t parse_sever_settings_message( ESP_LOGE(TAG, "couldn't get memory for " "server settings string"); - return PARSER_CRITICAL_ERROR; + // critical error + esp_restart(); } size_t tmpSize = base_message_rx->size - 4; // TODO: should there be an assert that tmpSize <= typedMsgLen? READ_DATA_WITH_CLEANUP(parser, serverSettingsString, tmpSize, - free(serverSettingsString) // This will be executed on error before returning + free(serverSettingsString) // This will be executed on + // error before returning ); serverSettingsString[typedMsgLen] = 0; @@ -295,39 +300,37 @@ parser_return_state_t parse_sever_settings_message( "Failed to read server " "settings: %d", deserialization_result); - return PARSER_CRITICAL_ERROR; + // critical error. A failed deserialization could potentially be a memory + // issue. + esp_restart(); } - return PARSER_COMPLETE; // do callback - + return PARSER_OK; // do callback } - parser_return_state_t parse_time_message(snapcast_protocol_parser_t* parser, base_message_t* base_message_rx, time_message_t* time_message_rx) { READ_TIMESTAMP(parser, time_message_rx->latency); - if (base_message_rx->size < 8) { // TODO: how to handle this case? Do we NEED to check? - ESP_LOGE(TAG, - "error time message, this shouldn't happen! %d %ld", - 8, base_message_rx->size); - return PARSER_INCOMPLETE; // use this return value as "ignore" + if (base_message_rx->size < + 8) { // TODO: how to handle this case? Do we NEED to check? + ESP_LOGE(TAG, "error time message, this shouldn't happen! %d %ld", 8, + base_message_rx->size); + return PARSER_RESTART_CONNECTION; } // ESP_LOGI(TAG, "done time message"); - return PARSER_COMPLETE; // do callback + return PARSER_OK; // do callback } -parser_return_state_t parse_unknown_message(snapcast_protocol_parser_t* parser, - base_message_t* base_message_rx) { +parser_return_state_t parser_skip_typed_message( + snapcast_protocol_parser_t* parser, base_message_t* base_message_rx) { // For unknown messages, we need to consume all remaining bytes char dummy_byte; for (uint32_t i = 0; i < base_message_rx->size; i++) { READ_BYTE(parser, dummy_byte); } - ESP_LOGI(TAG, "done unknown typed message %d", base_message_rx->type); - - return PARSER_COMPLETE; + return PARSER_OK; } diff --git a/components/net_functions/CMakeLists.txt b/components/net_functions/CMakeLists.txt index 90e2c5c7..a5290f90 100644 --- a/components/net_functions/CMakeLists.txt +++ b/components/net_functions/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "net_functions.c" INCLUDE_DIRS "include" - REQUIRES mdns network_interface driver) + REQUIRES mdns driver) diff --git a/components/settings_manager/settings_manager.c b/components/settings_manager/settings_manager.c index e5384d5d..875da80e 100644 --- a/components/settings_manager/settings_manager.c +++ b/components/settings_manager/settings_manager.c @@ -238,7 +238,7 @@ esp_err_t settings_get_mdns_enabled(bool *enabled) { #ifndef CONFIG_SNAPSERVER_USE_MDNS *enabled = false; #else - *enabled = true; + *enabled = CONFIG_SNAPSERVER_USE_MDNS; #endif ESP_LOGD(TAG, "%s: mdns from CONFIG_SNAPSERVER_USE_MDNS: %d", __func__, *enabled ? 1 : 0); xSemaphoreGive(hostname_mutex); diff --git a/components/snapclient/CMakeLists.txt b/components/snapclient/CMakeLists.txt new file mode 100644 index 00000000..d3655901 --- /dev/null +++ b/components/snapclient/CMakeLists.txt @@ -0,0 +1,23 @@ +idf_component_register(SRCS "snapclient_helper.c" "snapclient.c" "connection_handler.c" + INCLUDE_DIRS "include" + + PRIV_REQUIRES esp_timer mdns opus flac lightsnapcast + ) + +if(CONFIG_USE_DSP_PROCESSOR) + idf_component_optional_requires(PRIVATE dsp_processor) +endif() +if(CONFIG_SNAPSERVER_USE_MDNS) + idf_component_optional_requires(PRIVATE mdns) +endif() + +idf_component_optional_requires(PRIVATE net_functions network_interface settings_manager) +if(settings_manager IN_LIST BUILD_COMPONENTS) + target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DHAS_SETTINGS_MANAGER") +endif() +if(network_interface IN_LIST BUILD_COMPONENTS) + target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DHAS_NET_IF") +endif() +if(net_functions IN_LIST BUILD_COMPONENTS) + target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DHAS_NET_F") +endif() \ No newline at end of file diff --git a/main/Kconfig.projbuild b/components/snapclient/Kconfig.projbuild similarity index 98% rename from main/Kconfig.projbuild rename to components/snapclient/Kconfig.projbuild index d815e0dd..2e81bff7 100644 --- a/main/Kconfig.projbuild +++ b/components/snapclient/Kconfig.projbuild @@ -37,14 +37,6 @@ menu "Snapclient Configuration" help Name of the client to register the snapserver. - menu "HTTP Server Setting" - config WEB_PORT - int "User interface HTTP Server Port" - default 8000 - help - HTTP server port to use. - endmenu - config USE_SAMPLE_INSERTION bool "Use sample insertion" default y @@ -56,4 +48,12 @@ menu "Snapclient Configuration" Both approaches have similar performance keeping clients in sync <= 500µs + menu "HTTP Server Setting" + config WEB_PORT + int "User interface HTTP Server Port" + default 8000 + help + HTTP server port to use. + endmenu + endmenu diff --git a/components/snapclient/component.mk b/components/snapclient/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/components/snapclient/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/main/connection_handler.c b/components/snapclient/connection_handler.c similarity index 92% rename from main/connection_handler.c rename to components/snapclient/connection_handler.c index d4f2d57c..67da69b9 100644 --- a/main/connection_handler.c +++ b/components/snapclient/connection_handler.c @@ -3,10 +3,19 @@ #include "esp_log.h" #include "lwip/err.h" #include "lwip/netdb.h" +#if CONFIG_SNAPSERVER_USE_MDNS #include "mdns.h" +#endif +#ifdef HAS_NET_F #include "net_functions.h" +#endif +#ifdef HAS_NET_IF #include "network_interface.h" +#endif +#ifdef HAS_SETTINGS_MANAGER #include "settings_manager.h" +#endif +#include "snapclient_helper.h" // External variable that need to be accessible extern struct netconn* lwipNetconn; @@ -34,7 +43,7 @@ void setup_network(esp_netif_t** netif) { while (1) { #if CONFIG_SNAPCLIENT_USE_INTERNAL_ETHERNET || \ CONFIG_SNAPCLIENT_USE_SPI_ETHERNET - bool ethUp = network_is_netif_up(eth_netif); + bool ethUp = esp_netif_is_netif_up(eth_netif); if (ethUp) { *netif = eth_netif; @@ -43,7 +52,7 @@ void setup_network(esp_netif_t** netif) { } #endif - bool staUp = network_is_netif_up(sta_netif); + bool staUp = esp_netif_is_netif_up(sta_netif); if (staUp) { *netif = sta_netif; @@ -70,12 +79,10 @@ void setup_network(esp_netif_t** netif) { use_mdns = false; } #endif - if (use_mdns) { -#if CONFIG_SNAPSERVER_USE_MDNS +#ifdef CONFIG_SNAPSERVER_USE_MDNS ESP_LOGI(TAG, "Enable mdns"); mdns_init(); -#endif // Find snapcast server via mDNS mdns_result_t* r = NULL; esp_err_t err = 0; @@ -93,9 +100,11 @@ void setup_network(esp_netif_t** netif) { } } +#ifdef HAS_NET_F ESP_LOGI(TAG, "\n~~~~~~~~~~ MDNS Query success ~~~~~~~~~~"); mdns_print_results(r); ESP_LOGI(TAG, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); +#endif mdns_result_t* re = r; while (re) { @@ -130,12 +139,17 @@ void setup_network(esp_netif_t** netif) { continue; } +#if CONFIG_LWIP_IPV4 && !CONFIG_LWIP_IPV6 + remote_ip.addr = re->addr->addr.u_addr.ip4.addr; +#else ip_addr_copy(remote_ip, re->addr->addr); +#endif remotePort = r->port; mdns_query_results_free(r); ESP_LOGI(TAG, "Found %s:%d", ipaddr_ntoa(&remote_ip), remotePort); +#endif } else { // Use static server configuration from settings_manager char static_host[128] = {0}; @@ -172,6 +186,7 @@ void setup_network(esp_netif_t** netif) { ipaddr_ntoa(&remote_ip), remotePort); } +#if CONFIG_LWIP_IPV6 && CONFIG_LWIP_IPV4 if (remote_ip.type == IPADDR_TYPE_V4) { lwipNetconn = netconn_new(NETCONN_TCP); @@ -184,6 +199,15 @@ void setup_network(esp_netif_t** netif) { ESP_LOGW(TAG, "remote IP has unsupported IP type"); continue; } +#else +#if CONFIG_LWIP_IPV4 + lwipNetconn = netconn_new(NETCONN_TCP); + ESP_LOGV(TAG, "netconn using IPv4"); +#elif CONFIG_LWIP_IPV6 + lwipNetconn = netconn_new(NETCONN_TCP_IPV6); + ESP_LOGV(TAG, "netconn using IPv6"); +#endif +#endif if (lwipNetconn == NULL) { ESP_LOGE(TAG, "can't create netconn"); @@ -199,7 +223,7 @@ void setup_network(esp_netif_t** netif) { uint8_t netifIdx = esp_netif_get_netif_impl_index(*netif); rc1 = netconn_bind_if(lwipNetconn, netifIdx); if (rc1 != ERR_OK) { - ESP_LOGE(TAG, "can't bind interface %s", network_get_ifkey(*netif)); + ESP_LOGE(TAG, "can't bind interface %s", esp_netif_get_ifkey(*netif)); } #else // use IP to bind connection if (remote_ip.type == IPADDR_TYPE_V4) { @@ -233,7 +257,7 @@ void setup_network(esp_netif_t** netif) { continue; } - ESP_LOGI(TAG, "netconn connected using %s", network_get_ifkey(*netif)); + ESP_LOGI(TAG, "netconn connected using %s", esp_netif_get_ifkey(*netif)); break; // SUCCESS } } @@ -292,7 +316,7 @@ static int receive_data(struct netbuf** firstNetBuf, bool isMuted, network_get_netif_from_desc(NETWORK_INTERFACE_DESC_ETH); if (netif != eth_netif) { - bool ethUp = network_is_netif_up(eth_netif); + bool ethUp = esp_netif_is_netif_up(eth_netif); if (ethUp) { netconn_close(lwipNetconn); diff --git a/main/connection_handler.h b/components/snapclient/include/connection_handler.h similarity index 100% rename from main/connection_handler.h rename to components/snapclient/include/connection_handler.h diff --git a/components/snapclient/include/snapclient.h b/components/snapclient/include/snapclient.h new file mode 100644 index 00000000..5257c7d0 --- /dev/null +++ b/components/snapclient/include/snapclient.h @@ -0,0 +1,15 @@ +#ifndef __SNAPCLIENT_H__ +#define __SNAPCLIENT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +void init_snapcast(void (*set_volume)(int), void (*set_mute)(bool)); +void start_snapcast(); + +#ifdef __cplusplus +} +#endif + +#endif // __SNAPCLIENT_H__ diff --git a/components/snapclient/include/snapclient_helper.h b/components/snapclient/include/snapclient_helper.h new file mode 100644 index 00000000..93789f5d --- /dev/null +++ b/components/snapclient/include/snapclient_helper.h @@ -0,0 +1,35 @@ +#ifndef __SNAPCLIENT_HELPER_H__ +#define __SNAPCLIENT_HELPER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "esp_err.h" +#include "esp_netif.h" + +// Settings manager is an optional component that can be used to persist snapclient settings like server host and port, +// mdns enabled etc. If it's not included, default values from sdkconfig will be used (if set) or hardcoded defaults. +#ifndef HAS_SETTINGS_MANAGER +esp_err_t settings_get_mdns_enabled(bool* enabled); +esp_err_t settings_get_server_host(char *host, size_t max_len); +esp_err_t settings_get_server_port(int32_t* port); +esp_err_t settings_get_hostname(char *hostname, size_t max_len); +#endif + +// provide few functions to avoid including the whole network_interface component +#ifndef HAS_NET_IF +#define NETWORK_INTERFACE_DESC_STA "sta" +#define NETWORK_INTERFACE_DESC_ETH "eth" + +esp_netif_t *network_get_netif_from_desc(const char *desc); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __SNAPCLIENT_HELPER_H__ \ No newline at end of file diff --git a/components/snapclient/snapclient.c b/components/snapclient/snapclient.c new file mode 100644 index 00000000..96a0b763 --- /dev/null +++ b/components/snapclient/snapclient.c @@ -0,0 +1,1257 @@ +#include +#include + +#include "esp_log.h" +#include "esp_mac.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +// Opus decoder is implemented as a subcomponet from master git repo +#include "opus.h" + +// flac decoder is implemented as a subcomponet from master git repo +#include "FLAC/stream_decoder.h" + +#if CONFIG_USE_DSP_PROCESSOR +#include "dsp_processor.h" +#endif + +#include "connection_handler.h" +#include "player.h" +#ifdef HAS_SETTINGS_MANAGER +#include "settings_manager.h" +#endif +#include "snapcast.h" +#include "snapcast_protocol_parser.h" +#include "snapclient_helper.h" + +static bool isCachedChunk = false; +static uint32_t cachedBlocks = 0; + +static FLAC__StreamDecoderReadStatus read_callback( + const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, + void* client_data); +static FLAC__StreamDecoderWriteStatus write_callback( + const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, + const FLAC__int32* const buffer[], void* client_data); +static void metadata_callback(const FLAC__StreamDecoder* decoder, + const FLAC__StreamMetadata* metadata, + void* client_data); +static void error_callback(const FLAC__StreamDecoder* decoder, + FLAC__StreamDecoderErrorStatus status, + void* client_data); + +static FLAC__StreamDecoder* flacDecoder = NULL; + +const char* VERSION_STRING = "0.0.3"; + +#define HTTP_TASK_PRIORITY 17 +#define HTTP_TASK_CORE_ID 1 + +TaskHandle_t t_http_get_task = NULL; + +#define FAST_SYNC_LATENCY_BUF 10000 // in µs +#define NORMAL_SYNC_LATENCY_BUF 1000000 // in µs + +/* snapast parameters; configurable in menuconfig */ +#define SNAPCAST_USE_SOFT_VOL CONFIG_SNAPCLIENT_USE_SOFT_VOL + +/* Logging tag */ +static const char* TAG = "SC"; + +// static QueueHandle_t playerChunkQueueHandle = NULL; +SemaphoreHandle_t timeSyncSemaphoreHandle = NULL; + +SemaphoreHandle_t idCounterSemaphoreHandle = NULL; + +static void (*set_volume_cb)(int volume); +static void (*set_mute_cb)(bool mute); + +void time_sync_msg_cb(void* args); + +static char base_message_serialized[BASE_MESSAGE_SIZE]; + +// static const esp_timer_create_args_t tSyncArgs = { +// .callback = &time_sync_msg_cb, +// .dispatch_method = ESP_TIMER_TASK, +// .name = "tSyncMsg", +// .skip_unhandled_events = false}; + +struct netconn* lwipNetconn; + +static int id_counter = 0; + +static OpusDecoder* opusDecoder = NULL; + +static decoderData_t decoderChunk = { + .type = SNAPCAST_MESSAGE_INVALID, + .inData = NULL, + .timestamp = {0, 0}, + .outData = NULL, + .bytes = 0, +}; + +static decoderData_t pcmChunk = { + .type = SNAPCAST_MESSAGE_INVALID, + .inData = NULL, + .timestamp = {0, 0}, + .outData = NULL, + .bytes = 0, +}; + +/** + * + */ +void time_sync_msg_cb(void* args) { + base_message_t base_message_tx; + // struct timeval now; + int64_t now; + int rc1; + uint8_t p_pkt[BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE]; + + // uint8_t *p_pkt = (uint8_t *)malloc(BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE); + // if (p_pkt == NULL) { + // ESP_LOGW( + // TAG, + // "%s: Failed to get memory for time sync message. Skipping this + // round.", + // __func__); + // + // return; + // } + + memset(p_pkt, 0, BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE); + + base_message_tx.type = SNAPCAST_MESSAGE_TIME; + + xSemaphoreTake(idCounterSemaphoreHandle, portMAX_DELAY); + base_message_tx.id = id_counter++; + xSemaphoreGive(idCounterSemaphoreHandle); + + base_message_tx.refersTo = 0; + base_message_tx.received.sec = 0; + base_message_tx.received.usec = 0; + now = esp_timer_get_time(); + base_message_tx.sent.sec = now / 1000000; + base_message_tx.sent.usec = now - base_message_tx.sent.sec * 1000000; + base_message_tx.size = TIME_MESSAGE_SIZE; + rc1 = base_message_serialize(&base_message_tx, (char*)&p_pkt[0], + BASE_MESSAGE_SIZE); + if (rc1) { + ESP_LOGE(TAG, "Failed to serialize base message for time"); + + return; + } + + rc1 = netconn_write(lwipNetconn, p_pkt, BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE, + NETCONN_NOCOPY); + + if (rc1 != ERR_OK) { + ESP_LOGW(TAG, "error writing timesync msg"); + + return; + } + + // free(p_pkt); + + // ESP_LOGI(TAG, "%s: sent time sync message, %u", __func__, + // base_message_tx.id); +} + +typedef struct { + int64_t now; + int64_t lastTimeSync; + int64_t lastTimeSyncSent; + uint64_t timeout; +} time_sync_data_t; + +/** + * + */ +void time_sync_msg_received(base_message_t* base_message_rx, + time_message_t* time_message_rx, + time_sync_data_t* time_sync_data, + bool received_codec_header) { + int64_t tmpDiffToServer, trx, tdif, ttx, diff; + trx = (int64_t)base_message_rx->received.sec * 1000000LL + + (int64_t)base_message_rx->received.usec; + ttx = (int64_t)base_message_rx->sent.sec * 1000000LL + + (int64_t)base_message_rx->sent.usec; + tdif = trx - ttx; // T4-T3 + ttx = (int64_t)time_message_rx->latency.sec * 1000000LL + + (int64_t)time_message_rx->latency.usec; // T2-T1 + tmpDiffToServer = (ttx - tdif) / 2; //((T2-T1) - (-T3+T4))/2 + + // clear diffBuffer if last update is + // older than a minute + diff = time_sync_data->now - time_sync_data->lastTimeSync; + if (diff > 60000000LL) { + ESP_LOGW(TAG, + "Last time sync older " + "than a minute. " + "Clearing time buffer"); + + reset_latency_buffer(); + + time_sync_data->timeout = FAST_SYNC_LATENCY_BUF; + + netconn_set_recvtimeout(lwipNetconn, + time_sync_data->timeout / 1000); // timeout in ms + + // esp_timer_stop(time_sync_data->timeSyncMessageTimer); + // if (received_codec_header == true) { + // if (!esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { + // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, + // time_sync_data->timeout); + // } + // } + } + +#if USE_TIMEFILTER + player_latency_insert(tmpDiffToServer, (tdif + ttx) / 2, trx); +#else + player_latency_insert(tmpDiffToServer); +#endif + + // ESP_LOGI(TAG, "Current latency:%lld:", + // tmpDiffToServer); + + // store current time + time_sync_data->lastTimeSync = time_sync_data->now; + + if (received_codec_header == true) { + // if (!esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { + // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, + // time_sync_data->timeout); + // } + + bool is_full = false; + latency_buffer_full(&is_full); + if ((is_full == true) && + (time_sync_data->timeout < NORMAL_SYNC_LATENCY_BUF)) { + time_sync_data->timeout = NORMAL_SYNC_LATENCY_BUF; + netconn_set_recvtimeout(lwipNetconn, + time_sync_data->timeout / 1000); // timeout in ms + + ESP_LOGI(TAG, "latency buffer full"); + + // if (esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { + // esp_timer_stop(time_sync_data->timeSyncMessageTimer); + // } + + // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, + // time_sync_data->timeout); + } else if ((is_full == false) && + (time_sync_data->timeout > FAST_SYNC_LATENCY_BUF)) { + time_sync_data->timeout = FAST_SYNC_LATENCY_BUF; + netconn_set_recvtimeout(lwipNetconn, + time_sync_data->timeout / 1000); // timeout in ms + + ESP_LOGI(TAG, "latency buffer not full"); + + // if (esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { + // esp_timer_stop(time_sync_data->timeSyncMessageTimer); + // } + + // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, + // time_sync_data->timeout); + } + } +} + +/** + * + */ +static FLAC__StreamDecoderReadStatus read_callback( + const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, + void* client_data) { + snapcastSetting_t* scSet = (snapcastSetting_t*)client_data; + // decoderData_t *flacData; + + (void)scSet; + + // xQueueReceive(decoderReadQHdl, &flacData, portMAX_DELAY); + // if (xQueueReceive(decoderReadQHdl, &flacData, pdMS_TO_TICKS(100))) + if (decoderChunk.inData) { + // ESP_LOGI(TAG, "in flac read cb %ld %p", flacData->bytes, + // flacData->inData); + + if (decoderChunk.bytes <= 0) { + // free_flac_data(flacData); + + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } + + isCachedChunk = false; + + // if (flacData->inData == NULL) { + // free_flac_data(flacData); + // + // return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + // } + + if (decoderChunk.bytes <= *bytes) { + memcpy(buffer, decoderChunk.inData, decoderChunk.bytes); + *bytes = decoderChunk.bytes; + + // ESP_LOGW(TAG, "read all flac inData %d", *bytes); + + free(decoderChunk.inData); + decoderChunk.inData = NULL; + decoderChunk.bytes = 0; + } else { + memcpy(buffer, decoderChunk.inData, *bytes); + + memmove(decoderChunk.inData, decoderChunk.inData + *bytes, + decoderChunk.bytes - *bytes); + decoderChunk.bytes -= *bytes; + decoderChunk.inData = + (uint8_t*)realloc(decoderChunk.inData, decoderChunk.bytes); + + // ESP_LOGW(TAG, "didn't read all flac inData %d", *bytes); + // flacData->inData += *bytes; + // flacData->bytes -= *bytes; + } + + // free_flac_data(flacData); + + // xQueueSend (flacReadQHdl, &flacData, portMAX_DELAY); + + // xSemaphoreGive(decoderReadSemaphore); + + // ESP_LOGE(TAG, "%s: data processed", __func__); + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } else { + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } +} + +/** + * + */ +static FLAC__StreamDecoderWriteStatus write_callback( + const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, + const FLAC__int32* const buffer[], void* client_data) { + size_t i; + snapcastSetting_t* scSet = (snapcastSetting_t*)client_data; + + size_t bytes = frame->header.blocksize * frame->header.channels * + frame->header.bits_per_sample / 8; + + (void)decoder; + + if (isCachedChunk) { + cachedBlocks += frame->header.blocksize; + } + + // ESP_LOGI(TAG, "in flac write cb %ld %d, pcmChunk.bytes %ld", + // frame->header.blocksize, bytes, pcmChunk.bytes); + + if (frame->header.channels != scSet->ch) { + ESP_LOGE(TAG, + "ERROR: frame header reports different channel count %ld than " + "previous metadata block %d", + frame->header.channels, scSet->ch); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + if (frame->header.bits_per_sample != scSet->bits) { + ESP_LOGE(TAG, + "ERROR: frame header reports different bps %ld than previous " + "metadata block %d", + frame->header.bits_per_sample, scSet->bits); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + if (buffer[0] == NULL) { + ESP_LOGE(TAG, "ERROR: buffer [0] is NULL"); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + if (buffer[1] == NULL) { + ESP_LOGE(TAG, "ERROR: buffer [1] is NULL"); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + uint8_t* pcmData; + do { + pcmData = (uint8_t*)realloc(pcmChunk.outData, pcmChunk.bytes + bytes); + if (!pcmData) { + ESP_LOGW(TAG, + "%s, failed to allocate PCM chunk payload (%lu + %u bytes, free " + "heap %u, largest block %u), try again", + __func__, pcmChunk.bytes, bytes, + heap_caps_get_free_size(MALLOC_CAP_8BIT), + heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); + vTaskDelay(pdMS_TO_TICKS(5)); + // return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } +#if 0 // enable heap usage profiling + else { + static size_t largestFreeBlockMin = 10000000; + size_t largestFreeBlock = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); + static size_t freeSizeMin = 10000000; + if (largestFreeBlock < largestFreeBlockMin) { + largestFreeBlockMin = largestFreeBlock; + ESP_LOGI(TAG, "%s, free heap %u, largest block %u", __func__, + heap_caps_get_free_size(MALLOC_CAP_8BIT), + largestFreeBlockMin); + } + } +#endif + } while (!pcmData); + + pcmChunk.outData = pcmData; + + for (i = 0; i < frame->header.blocksize; i++) { + // write little endian + pcmChunk.outData[pcmChunk.bytes + 4 * i] = (uint8_t)(buffer[0][i]); + pcmChunk.outData[pcmChunk.bytes + 4 * i + 1] = (uint8_t)(buffer[0][i] >> 8); + pcmChunk.outData[pcmChunk.bytes + 4 * i + 2] = (uint8_t)(buffer[1][i]); + pcmChunk.outData[pcmChunk.bytes + 4 * i + 3] = (uint8_t)(buffer[1][i] >> 8); + } + + pcmChunk.bytes += bytes; + + scSet->chkInFrames = frame->header.blocksize; + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +/** + * + */ +void metadata_callback(const FLAC__StreamDecoder* decoder, + const FLAC__StreamMetadata* metadata, + void* client_data) { + snapcastSetting_t* scSet = (snapcastSetting_t*)client_data; + + (void)decoder; + + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { + // ESP_LOGI(TAG, "in flac meta cb"); + + // save for later + scSet->sr = metadata->data.stream_info.sample_rate; + scSet->ch = metadata->data.stream_info.channels; + scSet->bits = metadata->data.stream_info.bits_per_sample; + + ESP_LOGI(TAG, "fLaC sampleformat: %ld:%d:%d", scSet->sr, scSet->bits, + scSet->ch); + + // ESP_LOGE(TAG, "%s: data processed", __func__); + } +} + +/** + * + */ +void error_callback(const FLAC__StreamDecoder* decoder, + FLAC__StreamDecoderErrorStatus status, void* client_data) { + (void)decoder, (void)client_data; + + ESP_LOGE(TAG, "Got error callback: %s\n", + FLAC__StreamDecoderErrorStatusString[status]); +} + +/** + * + */ +void server_settings_msg_received( + server_settings_message_t* server_settings_message, + snapcastSetting_t* scSet) { + static int volume = 0; + // log mute state, buffer, latency + ESP_LOGI(TAG, "Buffer length: %ld", server_settings_message->buffer_ms); + ESP_LOGI(TAG, "Latency: %ld", server_settings_message->latency); + ESP_LOGI(TAG, "Mute: %d", server_settings_message->muted); + ESP_LOGI(TAG, "Setting volume: %ld", server_settings_message->volume); + + // Volume setting using ADF HAL + // abstraction + if (scSet->muted != server_settings_message->muted) { +#if SNAPCAST_USE_SOFT_VOL + if (server_settings_message->muted) { + dsp_processor_set_volome(0.0); + } else { + dsp_processor_set_volome((double)server_settings_message->volume / 100); + } +#endif + set_mute_cb(server_settings_message->muted); + } + + if (volume != server_settings_message->volume) { +#if SNAPCAST_USE_SOFT_VOL + if (!server_settings_message->muted) { + dsp_processor_set_volome((double)server_settings_message->volume / 100); + } +#else + set_volume_cb(server_settings_message->volume); +#endif + } + + scSet->muted = server_settings_message->muted; + volume = server_settings_message->volume; + + if (scSet->cDacLat_ms != server_settings_message->latency || + scSet->buf_ms != server_settings_message->buffer_ms) { + scSet->cDacLat_ms = server_settings_message->latency; + scSet->buf_ms = server_settings_message->buffer_ms; + + if (player_send_snapcast_setting(scSet) != pdPASS) { + ESP_LOGE(TAG, + "Failed to notify sync task. " + "Did you init player?"); + + esp_restart(); + } + } +} + +/** + * + */ +void codec_header_received(char* codecPayload, uint32_t codecPayloadLen, + codec_type_t codec, snapcastSetting_t* scSet, + time_sync_data_t* time_sync_data) { + // first ensure everything is set up + // correctly and resources are + // available + + if (flacDecoder != NULL) { + FLAC__stream_decoder_finish(flacDecoder); + FLAC__stream_decoder_delete(flacDecoder); + flacDecoder = NULL; + } + + if (opusDecoder != NULL) { + opus_decoder_destroy(opusDecoder); + opusDecoder = NULL; + } + + if (codec == OPUS) { + uint16_t channels; + uint32_t rate; + uint16_t bits; + + memcpy(&rate, codecPayload + 4, sizeof(rate)); + memcpy(&bits, codecPayload + 8, sizeof(bits)); + memcpy(&channels, codecPayload + 10, sizeof(channels)); + + scSet->codec = codec; + scSet->bits = bits; + scSet->ch = channels; + scSet->sr = rate; + + ESP_LOGI(TAG, "Opus sample format: %ld:%d:%d\n", rate, bits, channels); + + int error = 0; + + opusDecoder = opus_decoder_create(scSet->sr, scSet->ch, &error); + if (error != 0) { + ESP_LOGI(TAG, "Failed to init opus coder"); + esp_restart(); + } + + ESP_LOGI(TAG, "Initialized opus Decoder: %d", error); + } else if (codec == FLAC) { + decoderChunk.bytes = codecPayloadLen; + do { + decoderChunk.inData = (uint8_t*)malloc(decoderChunk.bytes); + vTaskDelay(pdMS_TO_TICKS(1)); + } while (decoderChunk.inData == NULL); + memcpy(decoderChunk.inData, codecPayload, codecPayloadLen); + decoderChunk.outData = NULL; + decoderChunk.type = SNAPCAST_MESSAGE_CODEC_HEADER; + + flacDecoder = FLAC__stream_decoder_new(); + if (flacDecoder == NULL) { + ESP_LOGE(TAG, "Failed to init flac decoder"); + esp_restart(); + } + + FLAC__StreamDecoderInitStatus init_status = + FLAC__stream_decoder_init_stream( + flacDecoder, read_callback, NULL, NULL, NULL, NULL, write_callback, + metadata_callback, error_callback, scSet); + if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + ESP_LOGE(TAG, "ERROR: initializing decoder: %s\n", + FLAC__StreamDecoderInitStatusString[init_status]); + + esp_restart(); + } + + FLAC__stream_decoder_process_until_end_of_metadata(flacDecoder); + + // ESP_LOGI(TAG, "%s: processed codec header", + // __func__); + } else if (codec == PCM) { + uint16_t channels; + uint32_t rate; + uint16_t bits; + + memcpy(&channels, codecPayload + 22, sizeof(channels)); + memcpy(&rate, codecPayload + 24, sizeof(rate)); + memcpy(&bits, codecPayload + 34, sizeof(bits)); + + scSet->codec = codec; + scSet->bits = bits; + scSet->ch = channels; + scSet->sr = rate; + + ESP_LOGI(TAG, "pcm sampleformat: %ld:%d:%d", scSet->sr, scSet->bits, + scSet->ch); + } else { + ESP_LOGE(TAG, + "codec header decoder " + "shouldn't get here after " + "codec string was detected"); + + esp_restart(); + } + + if (player_send_snapcast_setting(scSet) != pdPASS) { + ESP_LOGE(TAG, + "Failed to notify sync task. " + "Did you init player?"); + + esp_restart(); + } + + // ESP_LOGI(TAG, "done codec header msg"); + + // esp_timer_stop(time_sync_data->timeSyncMessageTimer); + // if (!esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { + // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, + // time_sync_data->timeout); + // } +} + +/** + * + */ +void handle_chunk_message(codec_type_t codec, snapcastSetting_t* scSet, + pcm_chunk_message_t** pcmData, + wire_chunk_message_t* wire_chnk) { + switch (codec) { + case OPUS: { + int frame_size = -1; + int samples_per_frame; + opus_int16* audio = NULL; + + samples_per_frame = + opus_packet_get_samples_per_frame(decoderChunk.inData, scSet->sr); + if (samples_per_frame < 0) { + ESP_LOGE(TAG, + "couldn't get samples per frame count " + "of packet"); + } + + scSet->chkInFrames = samples_per_frame; + + // ESP_LOGW(TAG, "%d, %llu, %llu", + // samples_per_frame, 1000000ULL * + // samples_per_frame / scSet->sr, + // 1000000ULL * + // wire_chnk->timestamp.sec + + // wire_chnk->timestamp.usec); + + // ESP_LOGW(TAG, "got OPUS decoded chunk size: %ld + // " "frames from encoded chunk with size %d, + // allocated audio buffer %d", scSet->chkInFrames, + // wire_chnk->size, samples_per_frame); + + size_t bytes; + do { + bytes = samples_per_frame * (scSet->ch * scSet->bits >> 3); + + while ((audio = (opus_int16*)realloc(audio, bytes)) == NULL) { + ESP_LOGE(TAG, + "couldn't realloc memory for OPUS " + "audio %d", + bytes); + + vTaskDelay(pdMS_TO_TICKS(1)); + } + + frame_size = + opus_decode(opusDecoder, decoderChunk.inData, decoderChunk.bytes, + (opus_int16*)audio, samples_per_frame, 0); + + samples_per_frame <<= 1; + } while (frame_size < 0); + + free(decoderChunk.inData); + decoderChunk.inData = NULL; + + pcm_chunk_message_t* new_pcmChunk = NULL; + + // ESP_LOGW(TAG, "OPUS decode: %d", frame_size); + + if (allocate_pcm_chunk_memory(&new_pcmChunk, bytes) < 0) { + *pcmData = NULL; + } else { + new_pcmChunk->timestamp = wire_chnk->timestamp; + + if (new_pcmChunk->fragment->payload) { + volatile uint32_t* sample; + uint32_t tmpData; + uint32_t cnt = 0; + + for (int i = 0; i < bytes; i += 4) { + sample = + (volatile uint32_t*)(&(new_pcmChunk->fragment->payload[i])); + tmpData = (((uint32_t)audio[cnt] << 16) & 0xFFFF0000) | + (((uint32_t)audio[cnt + 1] << 0) & 0x0000FFFF); + *sample = (volatile uint32_t)tmpData; + + cnt += 2; + } + } + + free(audio); + audio = NULL; + +#if CONFIG_USE_DSP_PROCESSOR + if (new_pcmChunk->fragment->payload) { + dsp_processor_worker((void*)new_pcmChunk, (void*)scSet); + } +#endif + + insert_pcm_chunk(new_pcmChunk); + } + + if (player_send_snapcast_setting(scSet) != pdPASS) { + ESP_LOGE(TAG, + "Failed to notify " + "sync task about " + "codec. Did you " + "init player?"); + + esp_restart(); + } + + break; + } + + case FLAC: { + isCachedChunk = true; + cachedBlocks = 0; + + while (decoderChunk.bytes > 0) { + if (FLAC__stream_decoder_process_single(flacDecoder) == 0) { + ESP_LOGE(TAG, + "%s: FLAC__stream_decoder_process_single " + "failed", + __func__); + + // TODO: should insert some abort condition? + vTaskDelay(pdMS_TO_TICKS(10)); + } + } + + // alternating chunk sizes need time stamp repair + if ((cachedBlocks > 0) && (scSet->sr != 0)) { + uint64_t diffUs = 1000000ULL * cachedBlocks / scSet->sr; + + uint64_t timestamp = + 1000000ULL * wire_chnk->timestamp.sec + wire_chnk->timestamp.usec; + + timestamp = timestamp - diffUs; + + wire_chnk->timestamp.sec = timestamp / 1000000ULL; + wire_chnk->timestamp.usec = timestamp % 1000000ULL; + } + + pcm_chunk_message_t* new_pcmChunk = NULL; + int32_t ret = allocate_pcm_chunk_memory(&new_pcmChunk, pcmChunk.bytes); + // int32_t ret = -1; + + scSet->chkInFrames = FLAC__stream_decoder_get_blocksize(flacDecoder); + + // ESP_LOGE (TAG, "block size: %ld", + // scSet->chkInFrames * scSet->bits / 8 * scSet->ch); + // ESP_LOGI(TAG, "new_pcmChunk with size %ld", + // new_pcmChunk->totalSize); + + if (ret == 0) { + pcm_chunk_fragment_t* fragment = new_pcmChunk->fragment; + uint32_t fragmentCnt = 0; + + if (fragment->payload != NULL) { + uint32_t frames = pcmChunk.bytes / (scSet->ch * (scSet->bits / 8)); + + for (int i = 0; i < frames; i++) { + // TODO: for now fragmented payload is not + // supported and the whole chunk is expected + // to be in the first fragment + uint32_t tmpData; + memcpy(&tmpData, &pcmChunk.outData[fragmentCnt], sizeof(uint32_t)); + + if (fragment != NULL) { + volatile uint32_t* test = + (volatile uint32_t*)(&(fragment->payload[fragmentCnt])); + *test = (volatile uint32_t)tmpData; + } + + fragmentCnt += sizeof(uint32_t); + // if (fragmentCnt >= fragment->size) { + // fragmentCnt = 0; + + // fragment = fragment->nextFragment; + // } + } + } + + new_pcmChunk->timestamp = wire_chnk->timestamp; + +#if CONFIG_USE_DSP_PROCESSOR + if (new_pcmChunk->fragment->payload) { + dsp_processor_worker((void*)new_pcmChunk, (void*)scSet); + } + +#endif + + insert_pcm_chunk(new_pcmChunk); + // free_pcm_chunk(new_pcmChunk); + // new_pcmChunk = NULL; + } else { + ESP_LOGE(TAG, "failed to allocate chunk"); + } + + free(pcmChunk.outData); + pcmChunk.outData = NULL; + pcmChunk.bytes = 0; + + if (player_send_snapcast_setting(scSet) != pdPASS) { + ESP_LOGE(TAG, + "Failed to " + "notify " + "sync task " + "about " + "codec. Did you " + "init player?"); + + esp_restart(); + } + + break; + } + + case PCM: { + size_t decodedSize = wire_chnk->size; + + // ESP_LOGW(TAG, "got PCM chunk," + // "typedMsgCurrentPos %d", + // parser.typedMsgCurrentPos); + + if (*pcmData) { + (*pcmData)->timestamp = wire_chnk->timestamp; + } + + scSet->chkInFrames = + decodedSize / ((size_t)scSet->ch * (size_t)(scSet->bits / 8)); + + // ESP_LOGW(TAG, + // "got PCM decoded chunk size: %ld + // frames", scSet->chkInFrames); + + if (player_send_snapcast_setting(scSet) != pdPASS) { + ESP_LOGE(TAG, + "Failed to notify " + "sync task about " + "codec. Did you " + "init player?"); + + esp_restart(); + } + +#if CONFIG_USE_DSP_PROCESSOR + if ((*pcmData) && ((*pcmData)->fragment->payload)) { + dsp_processor_worker((void*)(*pcmData), (void*)scSet); + } +#endif + if (*pcmData) { + insert_pcm_chunk(*pcmData); + } + + *pcmData = NULL; + free(decoderChunk.inData); + decoderChunk.inData = NULL; + + break; + } + + default: { + ESP_LOGE(TAG, + "Decoder (2) not " + "supported"); + + esp_restart(); + + break; + } + } +} + +/* + * returns: + * 0 if a message was (partially) processed sucessfully + * -1 if network needs restart + */ +int process_data(snapcast_protocol_parser_t* parser, + time_sync_data_t* time_sync_data, bool* received_codec_header, + codec_type_t* codec, snapcastSetting_t* scSet, + pcm_chunk_message_t** pcmData, bool paused) { + base_message_t base_message_rx; + + if (parse_base_message(parser, &base_message_rx) != PARSER_OK) { + return -1; // restart connection + } + time_sync_data->now = esp_timer_get_time(); + base_message_rx.received.sec = time_sync_data->now / 1000000; + base_message_rx.received.usec = + time_sync_data->now - base_message_rx.received.sec * 1000000; + + switch (base_message_rx.type) { + case SNAPCAST_MESSAGE_WIRE_CHUNK: { + wire_chunk_message_t wire_chnk = { + {0, 0}, 0, NULL}; // is wire_chnk.payload ever used? + + // skip this wires chunk message if codec header message was not received + // yet! + if (*received_codec_header == false || paused) { + if (parser_skip_typed_message(parser, &base_message_rx) != PARSER_OK) { + return -1; // restart connection + } + return 0; + } + + if (parse_wire_chunk_message(parser, &base_message_rx, *codec, pcmData, + &wire_chnk, &decoderChunk) != PARSER_OK) { + return -1; + } + handle_chunk_message(*codec, scSet, pcmData, &wire_chnk); + return 0; + } + + case SNAPCAST_MESSAGE_CODEC_HEADER: { + char* codecPayload = NULL; + uint32_t codecPayloadLen = 0; + int return_value = 0; + if (parse_codec_header_message(parser, received_codec_header, codec, + &codecPayload, + &codecPayloadLen) != PARSER_OK) { + return_value = -1; + } else { + codec_header_received(codecPayload, codecPayloadLen, *codec, scSet, + time_sync_data); + } + + // in all cases: free Payload + if (codecPayload != NULL) { + free(codecPayload); + } + + return return_value; + } + + case SNAPCAST_MESSAGE_SERVER_SETTINGS: { + server_settings_message_t server_settings_message; + if (parse_sever_settings_message(parser, &base_message_rx, + &server_settings_message) != PARSER_OK) { + return -1; + } + server_settings_msg_received(&server_settings_message, scSet); + return 0; + } + + case SNAPCAST_MESSAGE_TIME: { + time_message_t time_message_rx; + if (parse_time_message(parser, &base_message_rx, &time_message_rx) != + PARSER_OK) { + return -1; + } + time_sync_msg_received(&base_message_rx, &time_message_rx, time_sync_data, + *received_codec_header); + return 0; + } + + default: { + if (parser_skip_typed_message(parser, &base_message_rx) != PARSER_OK) { + return -1; + } + + ESP_LOGD(TAG, "done skipping typed message %d", base_message_rx.type); + return 0; + } + } + + return 0; +} + +typedef struct { + time_sync_data_t* time_sync_data; + bool* received_codec_header; +} before_receive_callback_data_t; + +void before_receive_callback(before_receive_callback_data_t* data) { + // unpack + time_sync_data_t* time_sync_data = data->time_sync_data; + bool received_codec_header = *data->received_codec_header; + + time_sync_data->now = esp_timer_get_time(); + // send time sync message + if ((received_codec_header && + (time_sync_data->now - time_sync_data->lastTimeSyncSent) >= + time_sync_data->timeout)) { + time_sync_msg_cb(NULL); + time_sync_data->lastTimeSyncSent = time_sync_data->now; + + // ESP_LOGI(TAG, "time sync sent after %lluus", timeout); + } +} + +void http_player_state_changed() { + xTaskNotifyGive(t_http_get_task); + // ESP_LOGI(TAG, "http task cb"); +} + +/** + * + */ +static void http_get_task(void* pvParameters) { + connection_t connection; + connection.firstNetBuf = NULL; + connection.rc1 = ERR_OK; + connection.netif = NULL; + int rc1; // for local scope (handshake), independent of connection.rc1 + base_message_t base_message_rx; + hello_message_t hello_message; + char* hello_message_serialized = NULL; + static char device_hostname[64] = {0}; // Buffer for hostname + int result; + time_sync_data_t time_sync_data; + time_sync_data.lastTimeSync = 0; + time_sync_data.lastTimeSyncSent = 0; + bool received_codec_header = false; + codec_type_t codec = NONE; + snapcastSetting_t scSet; + pcm_chunk_message_t* pcmData = NULL; + bool paused = false; + + // create a timer to send time sync messages every x µs + // esp_timer_create(&tSyncArgs, &time_sync_data.timeSyncMessageTimer); + + idCounterSemaphoreHandle = xSemaphoreCreateMutex(); + if (idCounterSemaphoreHandle == NULL) { + ESP_LOGE(TAG, "can't create id Counter Semaphore"); + + esp_restart(); + } + + add_player_state_cb(http_player_state_changed); + + while (1) { + // do some house keeping + { + // esp_timer_stop(time_sync_data.timeSyncMessageTimer); + + received_codec_header = false; + + xSemaphoreTake(idCounterSemaphoreHandle, portMAX_DELAY); + id_counter = 0; + xSemaphoreGive(idCounterSemaphoreHandle); + + if (opusDecoder != NULL) { + opus_decoder_destroy(opusDecoder); + opusDecoder = NULL; + } + + if (flacDecoder != NULL) { + FLAC__stream_decoder_finish(flacDecoder); + FLAC__stream_decoder_delete(flacDecoder); + flacDecoder = NULL; + } + + if (decoderChunk.inData) { + free(decoderChunk.inData); + decoderChunk.inData = NULL; + } + + if (decoderChunk.outData) { + free(decoderChunk.outData); + decoderChunk.outData = NULL; + } + } + + // NETWORK setup ends here ( or before getting mac address ) + setup_network(&connection.netif); + + // if (reset_latency_buffer() < 0) { + // ESP_LOGE(TAG, + // "reset_diff_buffer: couldn't reset median filter long. STOP"); + // return; + // } + + uint8_t base_mac[6]; +#if CONFIG_SNAPCLIENT_USE_INTERNAL_ETHERNET || \ + CONFIG_SNAPCLIENT_USE_SPI_ETHERNET + // Get MAC address for Eth Interface + char eth_mac_address[18]; + + esp_read_mac(base_mac, ESP_MAC_ETH); + sprintf(eth_mac_address, "%02X:%02X:%02X:%02X:%02X:%02X", base_mac[0], + base_mac[1], base_mac[2], base_mac[3], base_mac[4], base_mac[5]); + ESP_LOGI(TAG, "eth mac: %s", eth_mac_address); +#endif + // Get MAC address for WiFi station + char mac_address[18]; + esp_read_mac(base_mac, ESP_MAC_WIFI_STA); + sprintf(mac_address, "%02X:%02X:%02X:%02X:%02X:%02X", base_mac[0], + base_mac[1], base_mac[2], base_mac[3], base_mac[4], base_mac[5]); + ESP_LOGI(TAG, "sta mac: %s", mac_address); + + time_sync_data.now = esp_timer_get_time(); + + // init base message + base_message_rx.type = SNAPCAST_MESSAGE_HELLO; + xSemaphoreTake(idCounterSemaphoreHandle, portMAX_DELAY); + base_message_rx.id = id_counter++; + xSemaphoreGive(idCounterSemaphoreHandle); + + base_message_rx.refersTo = 0x0000; + base_message_rx.sent.sec = time_sync_data.now / 1000000; + base_message_rx.sent.usec = + time_sync_data.now - base_message_rx.sent.sec * 1000000; + base_message_rx.received.sec = 0; + base_message_rx.received.usec = 0; + base_message_rx.size = 0x00000000; + + // init hello message + hello_message.mac = mac_address; + + // Get hostname from NVS or fallback to a sensible default + if (settings_get_hostname(device_hostname, sizeof(device_hostname)) != + ESP_OK) { + strncpy(device_hostname, "snapclient", sizeof(device_hostname) - 1); + } + hello_message.hostname = device_hostname; + + hello_message.version = (char*)VERSION_STRING; + hello_message.client_name = "libsnapcast"; + hello_message.os = "esp32"; + hello_message.arch = "xtensa"; + hello_message.instance = 1; + hello_message.id = mac_address; + hello_message.protocol_version = 2; + + if (hello_message_serialized == NULL) { + hello_message_serialized = hello_message_serialize( + &hello_message, (size_t*)&(base_message_rx.size)); + if (!hello_message_serialized) { + ESP_LOGE(TAG, "Failed to serialize hello message"); + esp_restart(); + } + } + + result = base_message_serialize(&base_message_rx, base_message_serialized, + BASE_MESSAGE_SIZE); + if (result) { + ESP_LOGE(TAG, "Failed to serialize base message"); + esp_restart(); + } + + rc1 = netconn_write(lwipNetconn, base_message_serialized, BASE_MESSAGE_SIZE, + NETCONN_NOCOPY); + rc1 |= netconn_write(lwipNetconn, hello_message_serialized, + base_message_rx.size, NETCONN_NOCOPY); + if (rc1 != ERR_OK) { + ESP_LOGE(TAG, "netconn failed to send hello message"); + + continue; + } + + ESP_LOGI(TAG, "netconn sent hello message"); + + free(hello_message_serialized); + hello_message_serialized = NULL; + + // init default setting + scSet.buf_ms = 500; + scSet.codec = NONE; + scSet.bits = 16; + scSet.ch = 2; + scSet.sr = 44100; + scSet.chkInFrames = 0; + scSet.muted = true; + + snapcast_protocol_parser_t parser; + + // state machine starts here + + before_receive_callback_data_t before_receive_callback_data; + before_receive_callback_data.received_codec_header = &received_codec_header; + before_receive_callback_data.time_sync_data = &time_sync_data; + connection.before_receive_callback = + (void (*)(void*))before_receive_callback; + connection.before_receive_callback_data = + (void*)&before_receive_callback_data; + connection.isMuted = &scSet.muted; + + connection.firstNetBuf = NULL; + connection.first_receive = true; + connection.first_netbuf_processed = false; + + connection.state = CONNECTION_INITIALIZED; + + parser.get_byte_context = &connection; + parser.get_byte_function = (get_byte_callback_t)(&connection_get_byte); + + // as we need fast time syncs in the beginning we set receive timeout very + // low + time_sync_data.timeout = FAST_SYNC_LATENCY_BUF; + netconn_set_recvtimeout(lwipNetconn, + time_sync_data.timeout / 1000); // timeout in ms + + // Main connection loop - state machine + data processing + while (1) { + if (ulTaskNotifyTake(pdTRUE, 1) == pdTRUE) { + // state change, e.g. pause/play + paused = get_player_state() == PAUSED; + // ESP_LOGI(TAG, "http got cb. %s", paused ? "paused" : "playing/idle"); + } + int result = + process_data(&parser, &time_sync_data, &received_codec_header, &codec, + &scSet, &pcmData, paused); + if (result != 0) { + break; // restart connection + } + } + } +} + +/** + * + */ +int init_snapcast(void (*set_volume)(int), void (*set_mute)(bool)) { + if (set_volume == NULL) { + ESP_LOGE(TAG, "Volume callback is NULL"); + + return -1; + } + if (set_mute == NULL) { + ESP_LOGE(TAG, "Mute callback is NULL"); + + return -1; + } + set_volume_cb = set_volume; + set_mute_cb = set_mute; + return 0; +} + +void start_snapcast() { + xTaskCreatePinnedToCore(&http_get_task, "http", 15 * 1024, NULL, + HTTP_TASK_PRIORITY, &t_http_get_task, + HTTP_TASK_CORE_ID); + ESP_LOGD(TAG, "Started snapcast client task"); +} diff --git a/components/snapclient/snapclient_helper.c b/components/snapclient/snapclient_helper.c new file mode 100644 index 00000000..243c3a84 --- /dev/null +++ b/components/snapclient/snapclient_helper.c @@ -0,0 +1,62 @@ +#include "snapclient_helper.h" + +#include + + +// Settings manager is an optional component that can be used to persist snapclient settings like server host and port, +// mdns enabled etc. If it's not included, default values from sdkconfig will be used (if set) or hardcoded defaults. +#ifndef HAS_SETTINGS_MANAGER +esp_err_t settings_get_mdns_enabled(bool* enabled) { +#ifdef CONFIG_SNAPSERVER_USE_MDNS + *enabled = CONFIG_SNAPSERVER_USE_MDNS; +#else + *enabled = false; +#endif + return ESP_OK; +} + +esp_err_t settings_get_server_host(char *host, size_t max_len) { +#ifdef CONFIG_SNAPSERVER_HOST + strncpy(host, CONFIG_SNAPSERVER_HOST, max_len - 1); + host[max_len - 1] = '\0'; +#else + host[0] = '\0'; +#endif + return ESP_OK; +} + +esp_err_t settings_get_server_port(int32_t* port) { +#ifdef CONFIG_SNAPSERVER_PORT + *port = CONFIG_SNAPSERVER_PORT; +#else + *port = 0; +#endif + return ESP_OK; +} + +esp_err_t settings_get_hostname(char *hostname, size_t max_len) { +#ifdef CONFIG_SNAPCLIENT_NAME + strncpy(hostname, CONFIG_SNAPCLIENT_NAME, max_len - 1); + hostname[max_len - 1] = '\0'; +#else + // Ultimate fallback + strncpy(hostname, "esp32-snapclient", max_len - 1); + hostname[max_len - 1] = '\0'; +#endif + return ESP_OK; +} +#endif + +// provide few functions to avoid including the whole network_interface component +#ifndef HAS_NET_IF + +static bool netif_desc_matches_with(esp_netif_t *netif, void *ctx) { + return strcmp(ctx, esp_netif_get_desc(netif)) == 0; +} + +/** + */ +esp_netif_t *network_get_netif_from_desc(const char *desc) { + return esp_netif_find_if(netif_desc_matches_with, (void *)desc); +} +#endif \ No newline at end of file diff --git a/components/timefilter/TimeFilter.c b/components/timefilter/TimeFilter.c index 7175b7e2..84eb561a 100644 --- a/components/timefilter/TimeFilter.c +++ b/components/timefilter/TimeFilter.c @@ -22,12 +22,13 @@ int TIMEFILTER_Init(sTimeFilter_t *timeFilter, double process_std_dev, double drift_process_std_dev, double forget_factor, - double adaptive_cutoff, uint8_t min_samples) { + double adaptive_cutoff, uint8_t min_samples, double drift_significance_threshold) { if (timeFilter) { timeFilter->process_variance_ = process_std_dev * process_std_dev; timeFilter->drift_process_variance_ = drift_process_std_dev * drift_process_std_dev; timeFilter->forget_variance_factor_ = forget_factor * forget_factor; timeFilter->adaptive_forgetting_cutoff_ = adaptive_cutoff; + timeFilter->drift_significance_threshold_squared_ = drift_significance_threshold * drift_significance_threshold; timeFilter->min_samples_for_forgetting_ = min_samples; TIMEFILTER_Reset(timeFilter); return 0; @@ -127,6 +128,11 @@ void TIMEFILTER_Insert(sTimeFilter_t *timeFilter, int64_t measurement, int64_t m timeFilter->drift_covariance_ = new_drift_covariance - drift_gain * new_offset_drift_covariance; timeFilter->offset_drift_covariance_ = new_offset_drift_covariance - drift_gain * new_offset_covariance; timeFilter->offset_covariance_ = new_offset_covariance - offset_gain * new_offset_covariance; + + // Update drift significance flag for time conversion methods + // Only apply drift compensation if statistically significant (SNR check) + const double drift_squared = timeFilter->drift_ * timeFilter->drift_; + timeFilter->use_drift_ = drift_squared > timeFilter->drift_significance_threshold_squared_ * timeFilter->drift_covariance_; } int64_t TIMEFILTER_get_offset(sTimeFilter_t *timeFilter, int64_t client_time) { @@ -135,7 +141,9 @@ int64_t TIMEFILTER_get_offset(sTimeFilter_t *timeFilter, int64_t client_time) { // offset(t) = offset_base + drift * (t - t_last_update) double dt = client_time - timeFilter->last_update_; - int64_t offset = round(timeFilter->offset_ + timeFilter->drift_ * dt); + const double effective_drift = timeFilter->use_drift_ ? timeFilter->drift_ : 0.0; + + const int64_t offset = round(timeFilter->offset_ + effective_drift * dt); return offset; } @@ -147,6 +155,7 @@ void TIMEFILTER_Reset(sTimeFilter_t *timeFilter) { timeFilter->offset_drift_covariance_ = 0.0; timeFilter->drift_covariance_ = 0.0; timeFilter->last_update_ = 0; + timeFilter->use_drift_ = false; } uint32_t TIMEFILTER_isFull(sTimeFilter_t *timeFilter, uint32_t n) { diff --git a/components/timefilter/include/TimeFilter.h b/components/timefilter/include/TimeFilter.h index c5a44008..81fadbca 100644 --- a/components/timefilter/include/TimeFilter.h +++ b/components/timefilter/include/TimeFilter.h @@ -6,6 +6,7 @@ extern "C" { #endif #include +#include typedef struct { int64_t last_update_; @@ -19,11 +20,13 @@ typedef struct { double drift_process_variance_; double forget_variance_factor_; double adaptive_forgetting_cutoff_; + double drift_significance_threshold_squared_; + bool use_drift_; uint8_t min_samples_for_forgetting_; } sTimeFilter_t; int TIMEFILTER_Init(sTimeFilter_t *timeFilter, double process_std_dev, double drift_process_std_dev, double forget_factor, - double adaptive_cutoff, uint8_t min_samples); + double adaptive_cutoff, uint8_t min_samples, double drift_significance_threshold); void TIMEFILTER_Insert(sTimeFilter_t *timeFilter, int64_t measurement, int64_t max_error, int64_t time_added); int64_t TIMEFILTER_get_offset(sTimeFilter_t *timeFilter, int64_t client_time); void TIMEFILTER_Reset(sTimeFilter_t *timeFilter); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 6a855638..a6920b9b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,8 +1,8 @@ -idf_component_register(SRCS "main.c" "connection_handler.c" +idf_component_register(SRCS "main.c" INCLUDE_DIRS "." - PRIV_REQUIRES esp_timer esp_wifi nvs_flash audio_board audio_hal audio_sal net_functions opus flac ota_server - ui_http_server network_interface custom_board settings_manager tas5805m_settings + PRIV_REQUIRES esp_timer nvs_flash audio_board audio_hal audio_sal net_functions ota_server lightsnapcast dsp_processor + ui_http_server network_interface custom_board settings_manager tas5805m_settings snapclient ) set_source_files_properties(main.c PROPERTIES COMPILE_FLAGS -Wno-implicit-fallthrough) diff --git a/main/main.c b/main/main.c index 98ed9a69..f5f0ac30 100644 --- a/main/main.c +++ b/main/main.c @@ -2,40 +2,24 @@ #include #include "board.h" -#include "driver/timer_types_legacy.h" -#include "esp_event.h" #include "esp_log.h" -#include "esp_mac.h" -#include "esp_netif.h" -#include "esp_netif_ip_addr.h" #include "esp_pm.h" #include "esp_system.h" -#include "esp_timer.h" -#include "esp_wifi.h" #include "freertos/FreeRTOS.h" -#include "freertos/event_groups.h" -#include "freertos/idf_additions.h" -#include "freertos/portmacro.h" -#include "freertos/projdefs.h" #include "freertos/task.h" #include "hal/gpio_types.h" -#include "lwip/api.h" -#include "lwip/dns.h" -#include "lwip/err.h" -#include "lwip/ip_addr.h" -#include "lwip/netdb.h" -#include "lwip/netif.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/tcp.h" #include "mdns.h" #include "net_functions.h" #include "network_interface.h" #include "nvs_flash.h" - -// Web socket server -// #include "websocket_if.h" -// #include "websocket_server.h" +#if 0 +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" +#endif #include @@ -45,491 +29,90 @@ #include "dsp_processor_settings.h" #endif -// Opus decoder is implemented as a subcomponet from master git repo -#include "opus.h" - -// flac decoder is implemented as a subcomponet from master git repo -#include "FLAC/stream_decoder.h" -#include "connection_handler.h" +#include "snapclient.h" #include "ota_server.h" #include "player.h" #include "settings_manager.h" -#include "snapcast.h" -#include "snapcast_protocol_parser.h" #include "ui_http_server.h" #if CONFIG_DAC_TAS5805M #include "tas5805m_settings.h" #endif -static bool isCachedChunk = false; -static uint32_t cachedBlocks = 0; - -static FLAC__StreamDecoderReadStatus read_callback( - const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, - void *client_data); -static FLAC__StreamDecoderWriteStatus write_callback( - const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, - const FLAC__int32 *const buffer[], void *client_data); -static void metadata_callback(const FLAC__StreamDecoder *decoder, - const FLAC__StreamMetadata *metadata, - void *client_data); -static void error_callback(const FLAC__StreamDecoder *decoder, - FLAC__StreamDecoderErrorStatus status, - void *client_data); - -static FLAC__StreamDecoder *flacDecoder = NULL; - -const char *VERSION_STRING = "0.0.3"; - -#define HTTP_TASK_PRIORITY 17 -#define HTTP_TASK_CORE_ID 1 - #define OTA_TASK_PRIORITY 6 #define OTA_TASK_CORE_ID tskNO_AFFINITY // 1 // tskNO_AFFINITY +TaskHandle_t t_main_task = NULL; TaskHandle_t t_ota_task = NULL; -TaskHandle_t t_http_get_task = NULL; - -#define FAST_SYNC_LATENCY_BUF 10000 // in µs -#define NORMAL_SYNC_LATENCY_BUF 1000000 // in µs - -/* snapast parameters; configurable in menuconfig */ -#define SNAPCAST_USE_SOFT_VOL CONFIG_SNAPCLIENT_USE_SOFT_VOL /* Logging tag */ static const char *TAG = "SC"; -// static QueueHandle_t playerChunkQueueHandle = NULL; -SemaphoreHandle_t timeSyncSemaphoreHandle = NULL; - -SemaphoreHandle_t idCounterSemaphoreHandle = NULL; +SemaphoreHandle_t playerStateChangedMutex = NULL; typedef struct audioDACdata_s { - bool mute; + bool playerMute; + bool stateMute; int volume; - bool enabled; } audioDACdata_t; static audioDACdata_t audioDAC_data; static QueueHandle_t audioDACQHdl = NULL; static SemaphoreHandle_t audioDACSemaphore = NULL; -void time_sync_msg_cb(void *args); - -static char base_message_serialized[BASE_MESSAGE_SIZE]; - -//static const esp_timer_create_args_t tSyncArgs = { -// .callback = &time_sync_msg_cb, -// .dispatch_method = ESP_TIMER_TASK, -// .name = "tSyncMsg", -// .skip_unhandled_events = false}; - -struct netconn *lwipNetconn; - -static int id_counter = 0; - -static OpusDecoder *opusDecoder = NULL; - -static decoderData_t decoderChunk = { - .type = SNAPCAST_MESSAGE_INVALID, - .inData = NULL, - .timestamp = {0, 0}, - .outData = NULL, - .bytes = 0, -}; - -static decoderData_t pcmChunk = { - .type = SNAPCAST_MESSAGE_INVALID, - .inData = NULL, - .timestamp = {0, 0}, - .outData = NULL, - .bytes = 0, -}; - -/** - * - */ -void time_sync_msg_cb(void *args) { - base_message_t base_message_tx; - // struct timeval now; - int64_t now; - int rc1; - uint8_t p_pkt[BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE]; - -// uint8_t *p_pkt = (uint8_t *)malloc(BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE); -// if (p_pkt == NULL) { -// ESP_LOGW( -// TAG, -// "%s: Failed to get memory for time sync message. Skipping this round.", -// __func__); -// -// return; -// } - - memset(p_pkt, 0, BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE); - - base_message_tx.type = SNAPCAST_MESSAGE_TIME; - - xSemaphoreTake(idCounterSemaphoreHandle, portMAX_DELAY); - base_message_tx.id = id_counter++; - xSemaphoreGive(idCounterSemaphoreHandle); - - base_message_tx.refersTo = 0; - base_message_tx.received.sec = 0; - base_message_tx.received.usec = 0; - now = esp_timer_get_time(); - base_message_tx.sent.sec = now / 1000000; - base_message_tx.sent.usec = now - base_message_tx.sent.sec * 1000000; - base_message_tx.size = TIME_MESSAGE_SIZE; - rc1 = base_message_serialize(&base_message_tx, (char *)&p_pkt[0], - BASE_MESSAGE_SIZE); - if (rc1) { - ESP_LOGE(TAG, "Failed to serialize base message for time"); - - return; - } - - rc1 = netconn_write(lwipNetconn, p_pkt, BASE_MESSAGE_SIZE + TIME_MESSAGE_SIZE, - NETCONN_NOCOPY); - - if (rc1 != ERR_OK) { - ESP_LOGW(TAG, "error writing timesync msg"); - - return; - } - -// free(p_pkt); - - // ESP_LOGI(TAG, "%s: sent time sync message, %u", __func__, - // base_message_tx.id); -} - -typedef struct { - int64_t now; - int64_t lastTimeSync; - int64_t lastTimeSyncSent; - uint64_t timeout; -} time_sync_data_t; - -/** - * - */ -void time_sync_msg_received(base_message_t *base_message_rx, - time_message_t *time_message_rx, - time_sync_data_t *time_sync_data, - bool received_codec_header) { - int64_t tmpDiffToServer, trx, tdif, ttx, diff; - trx = (int64_t)base_message_rx->received.sec * 1000000LL + - (int64_t)base_message_rx->received.usec; - ttx = (int64_t)base_message_rx->sent.sec * 1000000LL + - (int64_t)base_message_rx->sent.usec; - tdif = trx - ttx; //T4-T3 - ttx = (int64_t)time_message_rx->latency.sec * 1000000LL + - (int64_t)time_message_rx->latency.usec; // T2-T1 - tmpDiffToServer = (ttx - tdif) / 2; //((T2-T1) - (-T3+T4))/2 - - // clear diffBuffer if last update is - // older than a minute - diff = time_sync_data->now - time_sync_data->lastTimeSync; - if (diff > 60000000LL) { - ESP_LOGW(TAG, - "Last time sync older " - "than a minute. " - "Clearing time buffer"); - - reset_latency_buffer(); - - time_sync_data->timeout = FAST_SYNC_LATENCY_BUF; - - netconn_set_recvtimeout(lwipNetconn, time_sync_data->timeout / 1000); // timeout in ms - - // esp_timer_stop(time_sync_data->timeSyncMessageTimer); - // if (received_codec_header == true) { - // if (!esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { - // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, - // time_sync_data->timeout); - // } - // } - } - -#if USE_TIMEFILTER - player_latency_insert(tmpDiffToServer, (tdif + ttx) / 2, trx); -#else - player_latency_insert(tmpDiffToServer); -#endif - - // ESP_LOGI(TAG, "Current latency:%lld:", - // tmpDiffToServer); - - // store current time - time_sync_data->lastTimeSync = time_sync_data->now; - - if (received_codec_header == true) { - // if (!esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { - // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, - // time_sync_data->timeout); - // } - - bool is_full = false; - latency_buffer_full(&is_full); - if ((is_full == true) && - (time_sync_data->timeout < NORMAL_SYNC_LATENCY_BUF)) { - time_sync_data->timeout = NORMAL_SYNC_LATENCY_BUF; - netconn_set_recvtimeout(lwipNetconn, time_sync_data->timeout / 1000); // timeout in ms - - ESP_LOGI(TAG, "latency buffer full"); - - // if (esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { - // esp_timer_stop(time_sync_data->timeSyncMessageTimer); - // } - - // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, - // time_sync_data->timeout); - } else if ((is_full == false) && - (time_sync_data->timeout > FAST_SYNC_LATENCY_BUF)) { - time_sync_data->timeout = FAST_SYNC_LATENCY_BUF; - netconn_set_recvtimeout(lwipNetconn, time_sync_data->timeout / 1000); // timeout in ms - - ESP_LOGI(TAG, "latency buffer not full"); - - // if (esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { - // esp_timer_stop(time_sync_data->timeSyncMessageTimer); - // } - - // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, - // time_sync_data->timeout); - } - } -} - -/** - * - */ -static FLAC__StreamDecoderReadStatus read_callback( - const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, - void *client_data) { - snapcastSetting_t *scSet = (snapcastSetting_t *)client_data; - // decoderData_t *flacData; - - (void)scSet; - - // xQueueReceive(decoderReadQHdl, &flacData, portMAX_DELAY); - // if (xQueueReceive(decoderReadQHdl, &flacData, pdMS_TO_TICKS(100))) - if (decoderChunk.inData) { - // ESP_LOGI(TAG, "in flac read cb %ld %p", flacData->bytes, - // flacData->inData); - - if (decoderChunk.bytes <= 0) { - // free_flac_data(flacData); - - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - } - - isCachedChunk = false; - - // if (flacData->inData == NULL) { - // free_flac_data(flacData); - // - // return FLAC__STREAM_DECODER_READ_STATUS_ABORT; - // } - - if (decoderChunk.bytes <= *bytes) { - memcpy(buffer, decoderChunk.inData, decoderChunk.bytes); - *bytes = decoderChunk.bytes; - - // ESP_LOGW(TAG, "read all flac inData %d", *bytes); - - free(decoderChunk.inData); - decoderChunk.inData = NULL; - decoderChunk.bytes = 0; - } else { - memcpy(buffer, decoderChunk.inData, *bytes); - - memmove(decoderChunk.inData, decoderChunk.inData + *bytes, - decoderChunk.bytes - *bytes); - decoderChunk.bytes -= *bytes; - decoderChunk.inData = - (uint8_t *)realloc(decoderChunk.inData, decoderChunk.bytes); - - // ESP_LOGW(TAG, "didn't read all flac inData %d", *bytes); - // flacData->inData += *bytes; - // flacData->bytes -= *bytes; - } - - // free_flac_data(flacData); - - // xQueueSend (flacReadQHdl, &flacData, portMAX_DELAY); - - // xSemaphoreGive(decoderReadSemaphore); - - // ESP_LOGE(TAG, "%s: data processed", __func__); - - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; - } else { - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - } -} - /** * */ -static FLAC__StreamDecoderWriteStatus write_callback( - const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, - const FLAC__int32 *const buffer[], void *client_data) { - size_t i; - snapcastSetting_t *scSet = (snapcastSetting_t *)client_data; - - size_t bytes = frame->header.blocksize * frame->header.channels * - frame->header.bits_per_sample / 8; - - (void)decoder; - - if (isCachedChunk) { - cachedBlocks += frame->header.blocksize; - } - - // ESP_LOGI(TAG, "in flac write cb %ld %d, pcmChunk.bytes %ld", - // frame->header.blocksize, bytes, pcmChunk.bytes); - - if (frame->header.channels != scSet->ch) { - ESP_LOGE(TAG, - "ERROR: frame header reports different channel count %ld than " - "previous metadata block %d", - frame->header.channels, scSet->ch); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - if (frame->header.bits_per_sample != scSet->bits) { - ESP_LOGE(TAG, - "ERROR: frame header reports different bps %ld than previous " - "metadata block %d", - frame->header.bits_per_sample, scSet->bits); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - if (buffer[0] == NULL) { - ESP_LOGE(TAG, "ERROR: buffer [0] is NULL"); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - if (buffer[1] == NULL) { - ESP_LOGE(TAG, "ERROR: buffer [1] is NULL"); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - uint8_t *pcmData; - do { - pcmData = (uint8_t *)realloc(pcmChunk.outData, pcmChunk.bytes + bytes); - if (!pcmData) { - ESP_LOGW(TAG, "%s, failed to allocate PCM chunk payload (%lu + %u bytes, free heap %u, largest block %u), try again", __func__, - pcmChunk.bytes, - bytes, - heap_caps_get_free_size(MALLOC_CAP_8BIT), - heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); - vTaskDelay(pdMS_TO_TICKS(5)); - // return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } -#if 0 // enable heap usage profiling - else { - static size_t largestFreeBlockMin = 10000000; - size_t largestFreeBlock = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); - static size_t freeSizeMin = 10000000; - if (largestFreeBlock < largestFreeBlockMin) { - largestFreeBlockMin = largestFreeBlock; - ESP_LOGI(TAG, "%s, free heap %u, largest block %u", __func__, - heap_caps_get_free_size(MALLOC_CAP_8BIT), - largestFreeBlockMin); - } +static void dac_control(audio_board_handle_t board_handle, + audioDACdata_t dac_data) { + static audioDACdata_t dac_data_old = { + .playerMute = true, + .stateMute = true, + .volume = -1, + }; + static bool muted = true; + // TODO: can and should we pass audio_hal_handle_t instead of + // audio_board_handle_t? + if (dac_data.playerMute != dac_data_old.playerMute || + dac_data.stateMute != dac_data_old.stateMute) { + // if either player or state mute is active, we need to mute the output + bool mute = dac_data.playerMute || dac_data.stateMute; + if (mute != muted) { + muted = mute; + audio_hal_set_mute(board_handle->audio_hal, muted); } -#endif - } while(!pcmData); - - pcmChunk.outData = pcmData; - - for (i = 0; i < frame->header.blocksize; i++) { - // write little endian - pcmChunk.outData[pcmChunk.bytes + 4 * i] = (uint8_t)(buffer[0][i]); - pcmChunk.outData[pcmChunk.bytes + 4 * i + 1] = (uint8_t)(buffer[0][i] >> 8); - pcmChunk.outData[pcmChunk.bytes + 4 * i + 2] = (uint8_t)(buffer[1][i]); - pcmChunk.outData[pcmChunk.bytes + 4 * i + 3] = (uint8_t)(buffer[1][i] >> 8); } - - pcmChunk.bytes += bytes; - - scSet->chkInFrames = frame->header.blocksize; - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} - -/** - * - */ -void metadata_callback(const FLAC__StreamDecoder *decoder, - const FLAC__StreamMetadata *metadata, - void *client_data) { - snapcastSetting_t *scSet = (snapcastSetting_t *)client_data; - - (void)decoder; - - if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { - // ESP_LOGI(TAG, "in flac meta cb"); - - // save for later - scSet->sr = metadata->data.stream_info.sample_rate; - scSet->ch = metadata->data.stream_info.channels; - scSet->bits = metadata->data.stream_info.bits_per_sample; - - ESP_LOGI(TAG, "fLaC sampleformat: %ld:%d:%d", scSet->sr, scSet->bits, - scSet->ch); - - // ESP_LOGE(TAG, "%s: data processed", __func__); + if (dac_data.volume != dac_data_old.volume) { + audio_hal_set_volume(board_handle->audio_hal, dac_data.volume); } + dac_data_old = dac_data; } /** - * - */ -void error_callback(const FLAC__StreamDecoder *decoder, - FLAC__StreamDecoderErrorStatus status, void *client_data) { - (void)decoder, (void)client_data; - - ESP_LOGE(TAG, "Got error callback: %s\n", - FLAC__StreamDecoderErrorStatusString[status]); -} - -/** - * + * Set mute state. If set_state is true, it reflects the snapclient state. Otherwise it + * is coming from player for temporary muting e.g. during startup. */ -void init_snapcast(QueueHandle_t audioQHdl) { - audioDACQHdl = audioQHdl; - audioDACSemaphore = xSemaphoreCreateMutex(); - audioDAC_data.mute = true; - audioDAC_data.volume = -1; - audioDAC_data.enabled = false; -} - -/** - * - */ -void audio_dac_enable(bool enabled) { +void audio_set_mute(bool mute, bool set_state) { xSemaphoreTake(audioDACSemaphore, portMAX_DELAY); - if (enabled != audioDAC_data.enabled) { - audioDAC_data.enabled = enabled; + if (set_state && (mute != audioDAC_data.stateMute)) { + audioDAC_data.stateMute = mute; xQueueOverwrite(audioDACQHdl, &audioDAC_data); } - xSemaphoreGive(audioDACSemaphore); -} - -/** - * - */ -void audio_set_mute(bool mute) { - xSemaphoreTake(audioDACSemaphore, portMAX_DELAY); - if (mute != audioDAC_data.mute) { - audioDAC_data.mute = mute; + else if (!set_state && mute != audioDAC_data.playerMute) { + audioDAC_data.playerMute = mute; xQueueOverwrite(audioDACQHdl, &audioDAC_data); } xSemaphoreGive(audioDACSemaphore); } +void player_set_mute(bool mute) { + audio_set_mute(mute, false); +} + +void set_mute_state(bool mute) { + audio_set_mute(mute, true); +} + /** * */ @@ -542,847 +125,69 @@ void audio_set_volume(int volume) { xSemaphoreGive(audioDACSemaphore); } -/** - * - */ -int server_settings_msg_received( - server_settings_message_t *server_settings_message, - snapcastSetting_t *scSet) { - // log mute state, buffer, latency - ESP_LOGI(TAG, "Buffer length: %ld", server_settings_message->buffer_ms); - ESP_LOGI(TAG, "Latency: %ld", server_settings_message->latency); - ESP_LOGI(TAG, "Mute: %d", server_settings_message->muted); - ESP_LOGI(TAG, "Setting volume: %ld", server_settings_message->volume); - - // Volume setting using ADF HAL - // abstraction - if (scSet->muted != server_settings_message->muted) { -#if SNAPCAST_USE_SOFT_VOL - if (server_settings_message->muted) { - dsp_processor_set_volome(0.0); - } else { - dsp_processor_set_volome((double)server_settings_message->volume / 100); - } -#endif - audio_set_mute(server_settings_message->muted); - } - - if (scSet->volume != server_settings_message->volume) { -#if SNAPCAST_USE_SOFT_VOL - if (!server_settings_message->muted) { - dsp_processor_set_volome((double)server_settings_message->volume / 100); - } -#else - audio_set_volume(server_settings_message->volume); -#endif +void player_state_changed() { + if (playerStateChangedMutex != NULL) { + xSemaphoreGive(playerStateChangedMutex); } - - scSet->cDacLat_ms = server_settings_message->latency; - scSet->buf_ms = server_settings_message->buffer_ms; - scSet->muted = server_settings_message->muted; - scSet->volume = server_settings_message->volume; - - if (player_send_snapcast_setting(scSet) != pdPASS) { - ESP_LOGE(TAG, - "Failed to notify sync task. " - "Did you init player?"); - - return -1; // fatal, this triggers return from http_get_task - } - return 0; + ESP_LOGI(TAG, "main task cb"); } -/** - * - */ -int codec_header_received(char *codecPayload, uint32_t codecPayloadLen, - codec_type_t codec, snapcastSetting_t *scSet, - time_sync_data_t *time_sync_data) { - // first ensure everything is set up - // correctly and resources are - // available - - if (flacDecoder != NULL) { - FLAC__stream_decoder_finish(flacDecoder); - FLAC__stream_decoder_delete(flacDecoder); - flacDecoder = NULL; - } - - if (opusDecoder != NULL) { - opus_decoder_destroy(opusDecoder); - opusDecoder = NULL; - } - - if (codec == OPUS) { - uint16_t channels; - uint32_t rate; - uint16_t bits; - - memcpy(&rate, codecPayload + 4, sizeof(rate)); - memcpy(&bits, codecPayload + 8, sizeof(bits)); - memcpy(&channels, codecPayload + 10, sizeof(channels)); - scSet->codec = codec; - scSet->bits = bits; - scSet->ch = channels; - scSet->sr = rate; - - ESP_LOGI(TAG, "Opus sample format: %ld:%d:%d\n", rate, bits, channels); - - int error = 0; - - opusDecoder = opus_decoder_create(scSet->sr, scSet->ch, &error); - if (error != 0) { - ESP_LOGI(TAG, "Failed to init opus coder"); - return -1; - } - - ESP_LOGI(TAG, "Initialized opus Decoder: %d", error); - } else if (codec == FLAC) { - decoderChunk.bytes = codecPayloadLen; - do { - decoderChunk.inData = (uint8_t *)malloc(decoderChunk.bytes); - vTaskDelay(pdMS_TO_TICKS(1)); - } while (decoderChunk.inData == NULL); - memcpy(decoderChunk.inData, codecPayload, codecPayloadLen); - decoderChunk.outData = NULL; - decoderChunk.type = SNAPCAST_MESSAGE_CODEC_HEADER; - - flacDecoder = FLAC__stream_decoder_new(); - if (flacDecoder == NULL) { - ESP_LOGE(TAG, "Failed to init flac decoder"); - return -1; - } - - FLAC__StreamDecoderInitStatus init_status = - FLAC__stream_decoder_init_stream( - flacDecoder, read_callback, NULL, NULL, NULL, NULL, write_callback, - metadata_callback, error_callback, scSet); - if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { - ESP_LOGE(TAG, "ERROR: initializing decoder: %s\n", - FLAC__StreamDecoderInitStatusString[init_status]); - - return -1; - } - - FLAC__stream_decoder_process_until_end_of_metadata(flacDecoder); - - // ESP_LOGI(TAG, "%s: processed codec header", - // __func__); - } else if (codec == PCM) { - uint16_t channels; - uint32_t rate; - uint16_t bits; - - memcpy(&channels, codecPayload + 22, sizeof(channels)); - memcpy(&rate, codecPayload + 24, sizeof(rate)); - memcpy(&bits, codecPayload + 34, sizeof(bits)); - - scSet->codec = codec; - scSet->bits = bits; - scSet->ch = channels; - scSet->sr = rate; - - ESP_LOGI(TAG, "pcm sampleformat: %ld:%d:%d", scSet->sr, scSet->bits, - scSet->ch); - } else { - ESP_LOGE(TAG, - "codec header decoder " - "shouldn't get here after " - "codec string was detected"); - - return -1; - } - - if (player_send_snapcast_setting(scSet) != pdPASS) { - ESP_LOGE(TAG, - "Failed to notify sync task. " - "Did you init player?"); - - return -1; - } - - // ESP_LOGI(TAG, "done codec header msg"); - - // esp_timer_stop(time_sync_data->timeSyncMessageTimer); - // if (!esp_timer_is_active(time_sync_data->timeSyncMessageTimer)) { - // esp_timer_start_periodic(time_sync_data->timeSyncMessageTimer, - // time_sync_data->timeout); - // } - return 0; +void log_mem() { + multi_heap_info_t info; + heap_caps_get_info(&info, MALLOC_CAP_8BIT); + ESP_LOGI(TAG, "Largest free block: %d bytes", info.largest_free_block); + ESP_LOGI(TAG, "Total free heap: %d bytes", info.total_free_bytes); + ESP_LOGI(TAG, "Minimum free heap ever: %d bytes", info.minimum_free_bytes); } -/** - * - */ -int handle_chunk_message(codec_type_t codec, snapcastSetting_t *scSet, - pcm_chunk_message_t **pcmData, - wire_chunk_message_t *wire_chnk) { - switch (codec) { - case OPUS: { - int frame_size = -1; - int samples_per_frame; - opus_int16 *audio = NULL; - - samples_per_frame = - opus_packet_get_samples_per_frame(decoderChunk.inData, scSet->sr); - if (samples_per_frame < 0) { - ESP_LOGE(TAG, - "couldn't get samples per frame count " - "of packet"); - } - - scSet->chkInFrames = samples_per_frame; - - // ESP_LOGW(TAG, "%d, %llu, %llu", - // samples_per_frame, 1000000ULL * - // samples_per_frame / scSet->sr, - // 1000000ULL * - // wire_chnk->timestamp.sec + - // wire_chnk->timestamp.usec); - - // ESP_LOGW(TAG, "got OPUS decoded chunk size: %ld - // " "frames from encoded chunk with size %d, - // allocated audio buffer %d", scSet->chkInFrames, - // wire_chnk->size, samples_per_frame); - - size_t bytes; - do { - bytes = samples_per_frame * (scSet->ch * scSet->bits >> 3); - - while ((audio = (opus_int16 *)realloc(audio, bytes)) == NULL) { - ESP_LOGE(TAG, - "couldn't realloc memory for OPUS " - "audio %d", - bytes); - - vTaskDelay(pdMS_TO_TICKS(1)); - } - - frame_size = - opus_decode(opusDecoder, decoderChunk.inData, decoderChunk.bytes, - (opus_int16 *)audio, samples_per_frame, 0); - - samples_per_frame <<= 1; - } while (frame_size < 0); - - free(decoderChunk.inData); - decoderChunk.inData = NULL; - - pcm_chunk_message_t *new_pcmChunk = NULL; - - // ESP_LOGW(TAG, "OPUS decode: %d", frame_size); - - if (allocate_pcm_chunk_memory(&new_pcmChunk, bytes) < 0) { - *pcmData = NULL; - } else { - new_pcmChunk->timestamp = wire_chnk->timestamp; - - if (new_pcmChunk->fragment->payload) { - volatile uint32_t *sample; - uint32_t tmpData; - uint32_t cnt = 0; - - for (int i = 0; i < bytes; i += 4) { - sample = - (volatile uint32_t *)(&(new_pcmChunk->fragment->payload[i])); - tmpData = (((uint32_t)audio[cnt] << 16) & 0xFFFF0000) | - (((uint32_t)audio[cnt + 1] << 0) & 0x0000FFFF); - *sample = (volatile uint32_t)tmpData; - - cnt += 2; - } - } - - free(audio); - audio = NULL; - -#if CONFIG_USE_DSP_PROCESSOR - if (new_pcmChunk->fragment->payload) { - dsp_processor_worker((void *)new_pcmChunk, (void *)scSet); - } -#endif - - insert_pcm_chunk(new_pcmChunk); - } - - if (player_send_snapcast_setting(scSet) != pdPASS) { - ESP_LOGE(TAG, - "Failed to notify " - "sync task about " - "codec. Did you " - "init player?"); - - return -1; - } - - break; - } - - case FLAC: { - isCachedChunk = true; - cachedBlocks = 0; - - while (decoderChunk.bytes > 0) { - if (FLAC__stream_decoder_process_single(flacDecoder) == 0) { - ESP_LOGE(TAG, - "%s: FLAC__stream_decoder_process_single " - "failed", - __func__); - - // TODO: should insert some abort condition? - vTaskDelay(pdMS_TO_TICKS(10)); - } - } - - // alternating chunk sizes need time stamp repair - if ((cachedBlocks > 0) && (scSet->sr != 0)) { - uint64_t diffUs = 1000000ULL * cachedBlocks / scSet->sr; - - uint64_t timestamp = - 1000000ULL * wire_chnk->timestamp.sec + wire_chnk->timestamp.usec; - - timestamp = timestamp - diffUs; - - wire_chnk->timestamp.sec = timestamp / 1000000ULL; - wire_chnk->timestamp.usec = timestamp % 1000000ULL; - } - - pcm_chunk_message_t *new_pcmChunk = NULL; - int32_t ret = allocate_pcm_chunk_memory(&new_pcmChunk, pcmChunk.bytes); -// int32_t ret = -1; - - scSet->chkInFrames = FLAC__stream_decoder_get_blocksize(flacDecoder); - - // ESP_LOGE (TAG, "block size: %ld", - // scSet->chkInFrames * scSet->bits / 8 * scSet->ch); - // ESP_LOGI(TAG, "new_pcmChunk with size %ld", - // new_pcmChunk->totalSize); - - if (ret == 0) { - pcm_chunk_fragment_t *fragment = new_pcmChunk->fragment; - uint32_t fragmentCnt = 0; - - if (fragment->payload != NULL) { - uint32_t frames = pcmChunk.bytes / (scSet->ch * (scSet->bits / 8)); - - for (int i = 0; i < frames; i++) { - // TODO: for now fragmented payload is not - // supported and the whole chunk is expected - // to be in the first fragment - uint32_t tmpData; - memcpy(&tmpData, &pcmChunk.outData[fragmentCnt], - sizeof(uint32_t)); - - if (fragment != NULL) { - volatile uint32_t *test = - (volatile uint32_t *)(&(fragment->payload[fragmentCnt])); - *test = (volatile uint32_t)tmpData; - } - - fragmentCnt += sizeof(uint32_t); - // if (fragmentCnt >= fragment->size) { - // fragmentCnt = 0; - - // fragment = fragment->nextFragment; - // } - } - } - - new_pcmChunk->timestamp = wire_chnk->timestamp; - -#if CONFIG_USE_DSP_PROCESSOR - if (new_pcmChunk->fragment->payload) { - dsp_processor_worker((void *)new_pcmChunk, (void *)scSet); - } - -#endif - - insert_pcm_chunk(new_pcmChunk); -// free_pcm_chunk(new_pcmChunk); -// new_pcmChunk = NULL; - } - else { - ESP_LOGE(TAG, "failed to allocate chunk"); - } - - free(pcmChunk.outData); - pcmChunk.outData = NULL; - pcmChunk.bytes = 0; - - if (player_send_snapcast_setting(scSet) != pdPASS) { - ESP_LOGE(TAG, - "Failed to " - "notify " - "sync task " - "about " - "codec. Did you " - "init player?"); - - return -1; - } - - break; - } - - case PCM: { - size_t decodedSize = wire_chnk->size; - - // ESP_LOGW(TAG, "got PCM chunk," - // "typedMsgCurrentPos %d", - // parser.typedMsgCurrentPos); - - if (*pcmData) { - (*pcmData)->timestamp = wire_chnk->timestamp; - } - - scSet->chkInFrames = - decodedSize / ((size_t)scSet->ch * (size_t)(scSet->bits / 8)); - - // ESP_LOGW(TAG, - // "got PCM decoded chunk size: %ld - // frames", scSet->chkInFrames); - - if (player_send_snapcast_setting(scSet) != pdPASS) { - ESP_LOGE(TAG, - "Failed to notify " - "sync task about " - "codec. Did you " - "init player?"); - - return -1; - } - -#if CONFIG_USE_DSP_PROCESSOR - if ((*pcmData) && ((*pcmData)->fragment->payload)) { - dsp_processor_worker((void *)(*pcmData), (void *)scSet); - } -#endif - if (*pcmData) { - insert_pcm_chunk(*pcmData); - } - - *pcmData = NULL; - free(decoderChunk.inData); - decoderChunk.inData = NULL; - - break; - } - - default: { - ESP_LOGE(TAG, - "Decoder (2) not " - "supported"); - - return -1; - - break; - } - } - return 0; -} - -/* - * returns: - * 0 if a message was (partially) processed sucessfully - * -1 if a critial error occured - * -2 if network needs restart - */ -int process_data(snapcast_protocol_parser_t *parser, - time_sync_data_t *time_sync_data, bool *received_codec_header, - codec_type_t *codec, snapcastSetting_t *scSet, - pcm_chunk_message_t **pcmData) { - base_message_t base_message_rx; - - if (parse_base_message(parser, &base_message_rx) == PARSER_COMPLETE) { - time_sync_data->now = esp_timer_get_time(); - base_message_rx.received.sec = time_sync_data->now / 1000000; - base_message_rx.received.usec = - time_sync_data->now - base_message_rx.received.sec * 1000000; - } else { // PARSER_CONNECTION_ERROR (only these two cases for base message) - return -2; // restart connection - } - - switch (base_message_rx.type) { - case SNAPCAST_MESSAGE_WIRE_CHUNK: { - wire_chunk_message_t wire_chnk = { - {0, 0}, 0, NULL}; // is wire_chnk.payload ever used? - switch (parse_wire_chunk_message(parser, &base_message_rx, - *received_codec_header, *codec, pcmData, - &wire_chnk, &decoderChunk)) { - case PARSER_COMPLETE: { - if (handle_chunk_message(*codec, scSet, pcmData, &wire_chnk) != 0) { - return -1; - } - break; - } - case PARSER_CRITICAL_ERROR: { - return -1; - } - case PARSER_INCOMPLETE: { - // need more data - return 0; - } - case PARSER_CONNECTION_ERROR: { - return -2; - } - } - break; - } - - case SNAPCAST_MESSAGE_CODEC_HEADER: { - char *codecPayload = NULL; - uint32_t codecPayloadLen = 0; - int return_value = 0; - switch (parse_codec_header_message(parser, received_codec_header, codec, - &codecPayload, &codecPayloadLen)) { - case PARSER_COMPLETE: { - if (codec_header_received(codecPayload, codecPayloadLen, *codec, - scSet, time_sync_data) != 0) { - return_value = -1; - } - break; - } - case PARSER_CRITICAL_ERROR: { - return_value = -1; - break; - } - case PARSER_CONNECTION_ERROR: { - return_value = -2; - break; - } - case PARSER_INCOMPLETE: { - // should not happen - // need more data - break; - } - } - - // in all cases: free Payload - if (codecPayload != NULL) { - free(codecPayload); - } - - return return_value; - } - - case SNAPCAST_MESSAGE_SERVER_SETTINGS: { - server_settings_message_t server_settings_message; - parser_return_state_t result = parse_sever_settings_message( - parser, &base_message_rx, &server_settings_message); - switch (result) { - case PARSER_COMPLETE: { - if (server_settings_msg_received(&server_settings_message, scSet) != - 0) { - return -1; +#if 0 +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_PIN_REQ_EVT: { + ESP_LOGI(TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit); + if (param->pin_req.min_16_digit) { + ESP_LOGI(TAG, "Input pin code: 0000 0000 0000 0000"); + esp_bt_pin_code_t pin_code = {1}; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code); + } else { + ESP_LOGI(TAG, "Input pin code: 1234"); + esp_bt_pin_code_t pin_code; + pin_code[0] = '6'; + pin_code[1] = '6'; + pin_code[2] = '6'; + pin_code[3] = '6'; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code); } break; } - case PARSER_CRITICAL_ERROR: { - return -1; - } - case PARSER_CONNECTION_ERROR: { - return -2; - } - case PARSER_INCOMPLETE: { - // should not happen - // need more data - break; + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(TAG, "Authentication success: %s", param->auth_cmpl.device_name); + } else { + ESP_LOGE(TAG, "Authentication failed, status: %d", param->auth_cmpl.stat); + } + break; } - } - break; - } - - case SNAPCAST_MESSAGE_TIME: { - time_message_t time_message_rx; - parser_return_state_t result = - parse_time_message(parser, &base_message_rx, &time_message_rx); - if (result == PARSER_COMPLETE) { - time_sync_msg_received(&base_message_rx, &time_message_rx, - time_sync_data, *received_codec_header); - } else if (result == PARSER_CONNECTION_ERROR) { - return -2; - } // could also be "incomplete", i.e. ignore content - break; - } - - default: { - if (parse_unknown_message(parser, &base_message_rx) == - PARSER_CONNECTION_ERROR) { - return -2; - } - break; + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %06"PRIu32, param->cfm_req.num_val); + esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); + break; + case ESP_BT_GAP_KEY_NOTIF_EVT: + ESP_LOGI(TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%06"PRIu32, param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; + case ESP_BT_GAP_MODE_CHG_EVT: + ESP_LOGI(TAG, "ESP_BT_GAP_MODE_CHG_EVT mode:%d", param->mode_chg.mode); + break; + default: + break; } - } - - return 0; -} - -typedef struct -{ - time_sync_data_t *time_sync_data; - bool *received_codec_header; -} before_receive_callback_data_t; - - -void before_receive_callback(before_receive_callback_data_t *data) { - //unpack - time_sync_data_t *time_sync_data = data->time_sync_data; - bool received_codec_header = *data->received_codec_header; - - time_sync_data->now = esp_timer_get_time(); - // send time sync message - if ((received_codec_header && (time_sync_data->now - time_sync_data->lastTimeSyncSent) >= time_sync_data->timeout)) { - time_sync_msg_cb(NULL); - time_sync_data->lastTimeSyncSent = time_sync_data->now; - - // ESP_LOGI(TAG, "time sync sent after %lluus", timeout); - } } - -/** - * - */ -static void http_get_task(void *pvParameters) { - connection_t connection; - connection.firstNetBuf = NULL; - connection.rc1 = ERR_OK; - connection.netif = NULL; - int rc1; // for local scope (handshake), independent of connection.rc1 - base_message_t base_message_rx; - hello_message_t hello_message; - char *hello_message_serialized = NULL; - static char device_hostname[64] = {0}; // Buffer for hostname - int result; - time_sync_data_t time_sync_data; - time_sync_data.lastTimeSync = 0; - time_sync_data.lastTimeSyncSent = 0; - bool received_codec_header = false; - codec_type_t codec = NONE; - snapcastSetting_t scSet; - pcm_chunk_message_t *pcmData = NULL; - - // create a timer to send time sync messages every x µs -// esp_timer_create(&tSyncArgs, &time_sync_data.timeSyncMessageTimer); - - idCounterSemaphoreHandle = xSemaphoreCreateMutex(); - if (idCounterSemaphoreHandle == NULL) { - ESP_LOGE(TAG, "can't create id Counter Semaphore"); - - return; - } - - while (1) { - // do some house keeping - { -// esp_timer_stop(time_sync_data.timeSyncMessageTimer); - - received_codec_header = false; - - xSemaphoreTake(idCounterSemaphoreHandle, portMAX_DELAY); - id_counter = 0; - xSemaphoreGive(idCounterSemaphoreHandle); - - if (opusDecoder != NULL) { - opus_decoder_destroy(opusDecoder); - opusDecoder = NULL; - } - - if (flacDecoder != NULL) { - FLAC__stream_decoder_finish(flacDecoder); - FLAC__stream_decoder_delete(flacDecoder); - flacDecoder = NULL; - } - - if (decoderChunk.inData) { - free(decoderChunk.inData); - decoderChunk.inData = NULL; - } - - if (decoderChunk.outData) { - free(decoderChunk.outData); - decoderChunk.outData = NULL; - } - } - - // NETWORK setup ends here ( or before getting mac address ) - setup_network(&connection.netif); - - //if (reset_latency_buffer() < 0) { - // ESP_LOGE(TAG, - // "reset_diff_buffer: couldn't reset median filter long. STOP"); - // return; - //} - - uint8_t base_mac[6]; -#if CONFIG_SNAPCLIENT_USE_INTERNAL_ETHERNET || \ - CONFIG_SNAPCLIENT_USE_SPI_ETHERNET - // Get MAC address for Eth Interface - char eth_mac_address[18]; - - esp_read_mac(base_mac, ESP_MAC_ETH); - sprintf(eth_mac_address, "%02X:%02X:%02X:%02X:%02X:%02X", base_mac[0], - base_mac[1], base_mac[2], base_mac[3], base_mac[4], base_mac[5]); - ESP_LOGI(TAG, "eth mac: %s", eth_mac_address); #endif - // Get MAC address for WiFi station - char mac_address[18]; - esp_read_mac(base_mac, ESP_MAC_WIFI_STA); - sprintf(mac_address, "%02X:%02X:%02X:%02X:%02X:%02X", base_mac[0], - base_mac[1], base_mac[2], base_mac[3], base_mac[4], base_mac[5]); - ESP_LOGI(TAG, "sta mac: %s", mac_address); - - time_sync_data.now = esp_timer_get_time(); - - // init base message - base_message_rx.type = SNAPCAST_MESSAGE_HELLO; - xSemaphoreTake(idCounterSemaphoreHandle, portMAX_DELAY); - base_message_rx.id = id_counter++; - xSemaphoreGive(idCounterSemaphoreHandle); - - base_message_rx.refersTo = 0x0000; - base_message_rx.sent.sec = time_sync_data.now / 1000000; - base_message_rx.sent.usec = - time_sync_data.now - base_message_rx.sent.sec * 1000000; - base_message_rx.received.sec = 0; - base_message_rx.received.usec = 0; - base_message_rx.size = 0x00000000; - - // init hello message - hello_message.mac = mac_address; - - // Get hostname from NVS or fallback to a sensible default - if (settings_get_hostname(device_hostname, sizeof(device_hostname)) != - ESP_OK) { - strncpy(device_hostname, "snapclient", sizeof(device_hostname) - 1); - } - hello_message.hostname = device_hostname; - - hello_message.version = (char *)VERSION_STRING; - hello_message.client_name = "libsnapcast"; - hello_message.os = "esp32"; - hello_message.arch = "xtensa"; - hello_message.instance = 1; - hello_message.id = mac_address; - hello_message.protocol_version = 2; - - if (hello_message_serialized == NULL) { - hello_message_serialized = hello_message_serialize( - &hello_message, (size_t *)&(base_message_rx.size)); - if (!hello_message_serialized) { - ESP_LOGE(TAG, "Failed to serialize hello message"); - return; - } - } - - result = base_message_serialize(&base_message_rx, base_message_serialized, - BASE_MESSAGE_SIZE); - if (result) { - ESP_LOGE(TAG, "Failed to serialize base message"); - return; - } - - rc1 = netconn_write(lwipNetconn, base_message_serialized, BASE_MESSAGE_SIZE, - NETCONN_NOCOPY); - rc1 |= netconn_write(lwipNetconn, hello_message_serialized, - base_message_rx.size, NETCONN_NOCOPY); - if (rc1 != ERR_OK) { - ESP_LOGE(TAG, "netconn failed to send hello message"); - - continue; - } - - ESP_LOGI(TAG, "netconn sent hello message"); - - free(hello_message_serialized); - hello_message_serialized = NULL; - - // init default setting - scSet.buf_ms = 500; - scSet.codec = NONE; - scSet.bits = 16; - scSet.ch = 2; - scSet.sr = 44100; - scSet.chkInFrames = 0; - scSet.volume = 0; - scSet.muted = true; - - snapcast_protocol_parser_t parser; - - // state machine starts here - - before_receive_callback_data_t before_receive_callback_data; - before_receive_callback_data.received_codec_header = &received_codec_header; - before_receive_callback_data.time_sync_data = &time_sync_data; - connection.before_receive_callback = (void (*)(void *))before_receive_callback; - connection.before_receive_callback_data = (void*) &before_receive_callback_data; - connection.isMuted = &scSet.muted; - - connection.firstNetBuf = NULL; - connection.first_receive = true; - connection.first_netbuf_processed = false; - - connection.state = CONNECTION_INITIALIZED; - - parser.get_byte_context = &connection; - parser.get_byte_function = (get_byte_callback_t)(&connection_get_byte); - - - // as we need fast time syncs in the beginning we set receive timeout very low - time_sync_data.timeout = FAST_SYNC_LATENCY_BUF; - netconn_set_recvtimeout(lwipNetconn, time_sync_data.timeout / 1000); // timeout in ms - - - // Main connection loop - state machine + data processing - while (1) { - int result = - process_data(&parser, &time_sync_data, &received_codec_header, &codec, - &scSet, &pcmData); - if (result == -1) { - return; // critical error in data processing - } else if (result == -2) { - break; // restart connection - } - } - } -} - -/** - * - */ -static void dac_control_task(audio_board_handle_t board_handle, - QueueHandle_t audioQHdl) { - audioDACdata_t dac_data; - audioDACdata_t dac_data_old = { - .mute = true, - .volume = -1, - .enabled = false, - }; - // TODO: can and should we pass audio_hal_handle_t instead of - // audio_board_handle_t? - while (1) { - if (xQueueReceive(audioQHdl, &dac_data, portMAX_DELAY) == pdTRUE) { - if (dac_data.mute != dac_data_old.mute) { - audio_hal_set_mute(board_handle->audio_hal, dac_data.mute); - } - if (dac_data.volume != dac_data_old.volume) { - audio_hal_set_volume(board_handle->audio_hal, dac_data.volume); - } - if (dac_data.enabled != dac_data_old.enabled) { - if (dac_data.enabled) { - audio_hal_ctrl_codec(board_handle->audio_hal, - AUDIO_HAL_CODEC_MODE_DECODE, - AUDIO_HAL_CTRL_START); - } else { - audio_hal_ctrl_codec(board_handle->audio_hal, - AUDIO_HAL_CODEC_MODE_DECODE, - AUDIO_HAL_CTRL_STOP); - } - } - dac_data_old = dac_data; - } - } -} /** * @@ -1396,7 +201,6 @@ void app_main(void) { } // ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(ret); - esp_log_level_set("*", ESP_LOG_INFO); // if enabled these cause a timer srv stack overflow @@ -1412,6 +216,9 @@ void app_main(void) { esp_log_level_set("dsp_settings", ESP_LOG_DEBUG); esp_log_level_set("UI_HTTP", ESP_LOG_WARN); esp_log_level_set("dspProc", ESP_LOG_DEBUG); + log_mem(); + + t_main_task = xTaskGetCurrentTaskHandle(); #if CONFIG_SNAPCLIENT_USE_INTERNAL_ETHERNET || \ CONFIG_SNAPCLIENT_USE_SPI_ETHERNET @@ -1542,10 +349,22 @@ void app_main(void) { }, }; - QueueHandle_t audioQHdl = xQueueCreate(1, sizeof(audioDACdata_t)); + audioDACQHdl = xQueueCreate(1, sizeof(audioDACdata_t)); + audioDACSemaphore = xSemaphoreCreateMutex(); + audioDAC_data.stateMute = true; + audioDAC_data.playerMute = true; + audioDAC_data.volume = -1; + + init_player(i2s_pin_config0, I2S_NUM_0, player_set_mute); + add_player_state_cb(player_state_changed); + init_snapcast(audio_set_volume, set_mute_state); - init_snapcast(audioQHdl); - init_player(i2s_pin_config0, I2S_NUM_0); + // Create binary semaphore for player state change notification + playerStateChangedMutex = xSemaphoreCreateBinary(); + if (playerStateChangedMutex == NULL) { + ESP_LOGE(TAG, "Failed to create playerStateChangedMutex"); + return; + } #if CONFIG_DAC_TAS5805M // Apply persisted TAS5805M settings now that the codec has been initialized @@ -1553,7 +372,6 @@ void app_main(void) { ESP_LOGW(TAG, "Failed to init persisted TAS5805M settings"); } #endif - network_if_init(); // Initialize settings manager (hostname + snapserver settings) @@ -1566,7 +384,7 @@ void app_main(void) { } ESP_LOGI(TAG, "Device hostname: %s", mdns_hostname); - init_http_server_task(); + //init_http_server_task(); // Enable websocket server // ESP_LOGI(TAG, "Setup ws server"); @@ -1579,15 +397,12 @@ void app_main(void) { #if CONFIG_USE_DSP_PROCESSOR dsp_processor_init(); // Must init processor first (creates mutexes/semaphores) - dsp_settings_init(); // Then settings can restore params into the processor + //dsp_settings_init(); // Then settings can restore params into the processor #endif xTaskCreatePinnedToCore(&ota_server_task, "ota", 14 * 256, NULL, OTA_TASK_PRIORITY, &t_ota_task, OTA_TASK_CORE_ID); - - xTaskCreatePinnedToCore(&http_get_task, "http", 15 * 1024, NULL, - HTTP_TASK_PRIORITY, &t_http_get_task, - HTTP_TASK_CORE_ID); + start_snapcast(); // while (1) { // // audio_event_iface_msg_t msg; @@ -1601,7 +416,71 @@ void app_main(void) { // continue; // } // } +#if 0 + esp_bt_controller_mem_release(ESP_BT_MODE_BLE); + esp_bt_mem_release(ESP_BT_MODE_BLE); + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ESP_LOGI(TAG, "BT Controller status before init: %d", esp_bt_controller_get_status()); + + esp_err_t err; + if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); + if ((err = esp_bluedroid_init_with_cfg(&bluedroid_cfg)) != ESP_OK) { + ESP_LOGE(TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + /* Set default parameters for Secure Simple Pairing */ + esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; + esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); + + esp_bt_dev_set_device_name("test_client"); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + esp_bt_gap_register_callback(&bt_app_gap_cb); + /* + * Set default parameters for Legacy Pairing + * Use variable pin, input pin code when pairing + */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; + esp_bt_pin_code_t pin_code; + esp_bt_gap_set_pin(pin_type, 0, pin_code); + + //shutdown bluetooth to save power, we don't need it for now and it causes some issues with wifi + if ((err = esp_bluedroid_disable()) != ESP_OK) { + ESP_LOGE(TAG, "%s disable bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + vTaskDelay(pdMS_TO_TICKS(100)); // give some time for bluedroid to shutdown before deinit + if ((err = esp_bluedroid_deinit()) != ESP_OK) { + ESP_LOGE(TAG, "%s deinit bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + vTaskDelay(pdMS_TO_TICKS(100)); // give some time for bluedroid to shutdown before deinit + + if ((err = esp_bt_controller_disable()) != ESP_OK) { + ESP_LOGE(TAG, "%s disable controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + vTaskDelay(pdMS_TO_TICKS(100)); // give some time for bluedroid to shutdown before deinit + if ((err = esp_bt_controller_deinit()) != ESP_OK) { + ESP_LOGE(TAG, "%s deinit controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + vTaskDelay(pdMS_TO_TICKS(100)); // give some time for bluedroid to shutdown before deinit +#endif #if CONFIG_PM_ENABLE // Configure dynamic frequency scaling: // automatic light sleep is enabled if tickless idle support is enabled. @@ -1614,6 +493,37 @@ void app_main(void) { }; esp_pm_configure(&pmConfig); #endif - - dac_control_task(board_handle, audioQHdl); + audioDACdata_t dac_data; + player_state_e state = IDLE; + int counter = 0; + while (1) { + if (xQueueReceive(audioDACQHdl, &dac_data, pdMS_TO_TICKS(100)) == pdTRUE) { + dac_control(board_handle, dac_data); + } + if (xSemaphoreTake(playerStateChangedMutex, pdMS_TO_TICKS(10)) == pdTRUE) { + player_state_e state_new = get_player_state(); + ESP_LOGI(TAG, "main got cb: %d", state_new); + if (state_new != state) { + ESP_LOGI(TAG, "Player state changed: %d -> %d", state, state_new); + if (state_new == PLAYING) { + audio_hal_ctrl_codec(board_handle->audio_hal, + AUDIO_HAL_CODEC_MODE_DECODE, + AUDIO_HAL_CTRL_START); + } else if (state == PLAYING) { + audio_hal_ctrl_codec(board_handle->audio_hal, + AUDIO_HAL_CODEC_MODE_DECODE, + AUDIO_HAL_CTRL_STOP); + } + state = state_new; + } + } + // test pause/play toggle every 200 loops + counter++; + if (counter % 100 == 0) { + //ESP_LOGI(TAG, "toggle pause: %d", state == PLAYING); + //pause_player(state == PLAYING); + log_mem(); + } + } } + diff --git a/partitions.csv b/partitions.csv index 04073e0a..b05ec5db 100644 --- a/partitions.csv +++ b/partitions.csv @@ -1,6 +1,6 @@ # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 32K, -otadata, data, ota, 0x11000, 8K, -phy_init, data, phy, 0x13000, 4K, -ota_0, app, ota_0, 0x20000, 1664K, -ota_1, app, ota_1, 0x1C0000, 1664K, +otadata, data, ota, , 8K, +phy_init, data, phy, , 4K, +ota_0, app, ota_0, , 1964K, +ota_1, app, ota_1, , 1964K, diff --git a/sdkconfig_MAX98357A_ESP32-S2 b/sdkconfig_MAX98357A_ESP32-S2 index dd98d5dd..ce34fb16 100644 --- a/sdkconfig_MAX98357A_ESP32-S2 +++ b/sdkconfig_MAX98357A_ESP32-S2 @@ -490,7 +490,6 @@ CONFIG_MAX98357_MUTE_PIN=13 # ESP32 DSP processor config # CONFIG_USE_DSP_PROCESSOR=y -# CONFIG_USE_BIQUAD_ASM is not set CONFIG_SNAPCLIENT_USE_SOFT_VOL=y # end of ESP32 DSP processor config diff --git a/sdkconfig_PCM5102A b/sdkconfig_PCM5102A index f287231c..445c1c00 100644 --- a/sdkconfig_PCM5102A +++ b/sdkconfig_PCM5102A @@ -427,7 +427,6 @@ CONFIG_PCM5102A_MUTE_PIN=33 # CONFIG_USE_DSP_PROCESSOR=y # CONFIG_SNAPCLIENT_MIX_LR_TO_MONO is not set -CONFIG_USE_BIQUAD_ASM=y CONFIG_SNAPCLIENT_USE_SOFT_VOL=y # end of ESP32 DSP processor config diff --git a/sdkconfig_PCM5102A_Olimex-ESP32-PoE b/sdkconfig_PCM5102A_Olimex-ESP32-PoE index 7d7e5aba..66d736c9 100644 --- a/sdkconfig_PCM5102A_Olimex-ESP32-PoE +++ b/sdkconfig_PCM5102A_Olimex-ESP32-PoE @@ -465,7 +465,6 @@ CONFIG_PCM5102A_MUTE_PIN=33 # ESP32 DSP processor config # CONFIG_USE_DSP_PROCESSOR=y -CONFIG_USE_BIQUAD_ASM=y CONFIG_SNAPCLIENT_USE_SOFT_VOL=y # end of ESP32 DSP processor config diff --git a/sdkconfig_PCM5102A_WT32-ETH01 b/sdkconfig_PCM5102A_WT32-ETH01 index 36f82ca9..c9703d00 100644 --- a/sdkconfig_PCM5102A_WT32-ETH01 +++ b/sdkconfig_PCM5102A_WT32-ETH01 @@ -427,7 +427,6 @@ CONFIG_PCM5102A_MUTE_PIN=5 # ESP32 DSP processor config # CONFIG_USE_DSP_PROCESSOR=y -CONFIG_USE_BIQUAD_ASM=y CONFIG_SNAPCLIENT_USE_SOFT_VOL=y # end of ESP32 DSP processor config