2020-04-23 00:15:11 +02:00
# include <string.h>
2020-12-13 22:19:40 +01:00
2020-04-23 00:15:11 +02:00
# include "freertos/FreeRTOS.h"
# include "freertos/task.h"
# include "freertos/event_groups.h"
2020-12-13 22:19:40 +01:00
# include "freertos/semphr.h"
# include "esp_log.h"
2021-03-06 07:23:44 +01:00
# include "esp_timer.h"
2020-12-13 22:19:40 +01:00
# include "sdkconfig.h"
2020-04-23 00:15:11 +02:00
# include "esp_wifi.h"
2020-12-13 22:19:40 +01:00
# include "esp_system.h"
2020-04-23 00:15:11 +02:00
# include "esp_event.h"
# include "nvs_flash.h"
2021-02-12 14:54:08 +01:00
# include "esp_types.h"
# include "driver/periph_ctrl.h"
# include "driver/timer.h"
2020-12-13 22:19:40 +01:00
# include "audio_element.h"
# include "audio_pipeline.h"
# include "audio_event_iface.h"
# include "i2s_stream.h"
# include "raw_stream.h"
# include "mp3_decoder.h"
# include "flac_decoder.h"
2020-12-20 00:41:32 +01:00
# include "auto_flac_dec.h"
2020-12-13 22:19:40 +01:00
# include "esp_peripherals.h"
# include "periph_spiffs.h"
2020-06-11 00:32:21 +02:00
# include "board.h"
2020-12-13 22:19:40 +01:00
//#include "es8388.h"
2020-04-23 00:15:11 +02:00
# include "lwip/err.h"
# include "lwip/sockets.h"
# include "lwip/sys.h"
# include "lwip/netdb.h"
# include "lwip/dns.h"
2020-05-31 00:55:20 +02:00
# include "mdns.h"
2020-04-23 00:15:11 +02:00
# include "esp_sntp.h"
2020-12-13 22:19:40 +01:00
2021-04-23 22:33:05 +02:00
# include <wifi_provisioning/manager.h>
# include <wifi_provisioning/scheme_softap.h>
2020-04-23 00:15:11 +02:00
# include "snapcast.h"
2021-04-22 18:15:21 +02:00
# include "MedianFilter.h"
2020-04-23 00:15:11 +02:00
2021-02-15 20:53:31 +01:00
# include <math.h>
2020-04-23 00:15:11 +02:00
# include <sys/time.h>
2021-05-16 09:18:27 +02:00
# include "opus.h"
2021-05-03 06:39:30 +02:00
# define CONFIG_USE_WIFI_PROVISIONING 0
2021-02-12 14:54:08 +01:00
# define COLLECT_RUNTIME_STATS 0
2021-02-25 23:54:59 +01:00
// @ 48kHz, 2ch, 16bit audio data and 24ms wirechunks (hardcoded for now) we expect 0.024 * 2 * 16/8 * 48000 = 4608 Bytes
2021-05-16 09:18:27 +02:00
# define WIRE_CHUNK_DURATION_MS 20UL //24UL // stream read chunk size [ms]
2021-02-25 23:54:59 +01:00
# define SAMPLE_RATE 48000UL
# define CHANNELS 2UL
2021-04-22 18:15:21 +02:00
# define BITS_PER_SAMPLE 16UL
2021-02-25 23:54:59 +01:00
2021-05-11 05:44:56 +02:00
const size_t chunkInBytes = ( WIRE_CHUNK_DURATION_MS * SAMPLE_RATE * CHANNELS * ( BITS_PER_SAMPLE / 8 ) ) / 1000 ;
2021-04-23 08:46:23 +02:00
const char * VERSION_STRING = " 0.1.0 " ;
2021-02-25 23:54:59 +01:00
2021-02-15 20:53:31 +01:00
/**
* @ brief Pre define APLL parameters , save compute time
* | bits_per_sample | rate | sdm0 | sdm1 | sdm2 | odir
2021-02-25 23:54:59 +01:00
*
* apll_freq = xtal_freq * ( 4 + sdm2 + sdm1 / 256 + sdm0 / 65536 ) / ( ( o_div + 2 ) * 2 )
* I2S bit clock is ( apll_freq / 16 )
2021-02-15 20:53:31 +01:00
*/
static const int apll_predefine [ ] [ 6 ] = {
{ 16 , 11025 , 38 , 80 , 5 , 31 } ,
{ 16 , 16000 , 147 , 107 , 5 , 21 } ,
{ 16 , 22050 , 130 , 152 , 5 , 15 } ,
{ 16 , 32000 , 129 , 212 , 5 , 10 } ,
{ 16 , 44100 , 15 , 8 , 5 , 6 } ,
{ 16 , 48000 , 136 , 212 , 5 , 6 } ,
{ 16 , 96000 , 143 , 212 , 5 , 2 } ,
{ 0 , 0 , 0 , 0 , 0 , 0 }
} ;
2021-02-25 23:54:59 +01:00
static const int apll_predefine_48k_corr [ ] [ 6 ] = {
{ 16 , 48048 , 27 , 215 , 5 , 6 } , // ~ 48kHz * 1.001
{ 16 , 47952 , 20 , 210 , 5 , 6 } , // ~ 48kHz * 0.999
2021-04-22 18:15:21 +02:00
{ 16 , 48005 , 213 , 212 , 5 , 6 } , // ~ 48kHz * 1.0001
2021-03-06 07:23:44 +01:00
{ 16 , 47995 , 84 , 212 , 5 , 6 } , // ~ 48kHz * 0.9999
2021-02-25 23:54:59 +01:00
} ;
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT ( ) ;
2021-04-22 18:15:21 +02:00
xQueueHandle i2s_event_queue ;
2021-05-16 09:18:27 +02:00
OpusDecoder * opusDecoder = NULL ;
2021-02-12 14:54:08 +01:00
audio_pipeline_handle_t flacDecodePipeline ;
2021-04-23 08:46:23 +02:00
audio_element_handle_t raw_stream_writer_to_decoder , decoder ;
2020-12-22 20:47:33 +01:00
2021-05-11 05:44:56 +02:00
ringbuf_handle_t i2sRingBufferHandle = NULL ;
2020-12-22 20:47:33 +01:00
uint64_t wirechnkCnt = 0 ;
uint64_t pcmchnkCnt = 0 ;
2021-02-12 14:54:08 +01:00
TaskHandle_t syncTaskHandle = NULL ;
2021-05-11 05:44:56 +02:00
TaskHandle_t i2STaskHandle = NULL ;
2020-12-22 20:47:33 +01:00
2020-12-13 22:19:40 +01:00
# define CONFIG_USE_SNTP 0
2021-05-16 09:18:27 +02:00
# define DAC_OUT_BUFFER_TIME_US 0
2021-05-16 11:19:58 +02:00
int64_t i2sDmaLAtency = 0 ;
2020-12-13 22:19:40 +01:00
2021-02-12 14:54:08 +01:00
static const char * TAG = " SC " ;
2020-12-13 22:19:40 +01:00
static int sntp_synced = 0 ;
char * codecString = NULL ;
2021-05-16 09:18:27 +02:00
int i2sDmaBufCnt = 0 ;
2020-12-13 22:19:40 +01:00
// configMAX_PRIORITIES - 1
// TODO: what are the best values here?
2021-05-03 06:39:30 +02:00
# define SYNC_TASK_PRIORITY 7 //configMAX_PRIORITIES - 2
2021-03-06 07:23:44 +01:00
# define SYNC_TASK_CORE_ID tskNO_AFFINITY //1//tskNO_AFFINITY
2021-02-12 14:54:08 +01:00
# define TIMESTAMP_TASK_PRIORITY 6
2021-04-22 18:15:21 +02:00
# define TIMESTAMP_TASK_CORE_ID tskNO_AFFINITY //1//tskNO_AFFINITY
2020-12-13 22:19:40 +01:00
2021-03-06 07:23:44 +01:00
# define HTTP_TASK_PRIORITY 6
# define HTTP_TASK_CORE_ID tskNO_AFFINITY //0//tskNO_AFFINITY
2020-12-13 22:19:40 +01:00
2021-05-11 05:44:56 +02:00
# define I2S_TASK_PRIORITY 8 //6//configMAX_PRIORITIES - 1
2021-03-06 07:23:44 +01:00
# define I2S_TASK_CORE_ID tskNO_AFFINITY //1//tskNO_AFFINITY
2020-12-13 22:19:40 +01:00
2020-12-22 20:47:33 +01:00
# define FLAC_DECODER_PRIORITY 6
2021-03-06 07:23:44 +01:00
# define FLAC_DECODER_CORE_ID tskNO_AFFINITY //0//tskNO_AFFINITY
2020-12-20 00:41:32 +01:00
QueueHandle_t timestampQueueHandle ;
2021-04-23 22:33:05 +02:00
# define TIMESTAMP_QUEUE_LENGTH 1000 //!< needs to be at least ~500 because if silence is received, the espressif's flac decoder won't generate data on its output for a long time
2020-12-20 00:41:32 +01:00
static StaticQueue_t timestampQueue ;
uint8_t timestampQueueStorageArea [ TIMESTAMP_QUEUE_LENGTH * sizeof ( tv_t ) ] ;
QueueHandle_t pcmChunkQueueHandle ;
2021-04-23 22:33:05 +02:00
# define PCM_CHNK_QUEUE_LENGTH 500 // TODO: one chunk is hardcoded to 24ms, change it to be dynamically adjustable. 1s buffer ~ 42
2020-12-20 00:41:32 +01:00
static StaticQueue_t pcmChunkQueue ;
uint8_t pcmChunkQueueStorageArea [ PCM_CHNK_QUEUE_LENGTH * sizeof ( wire_chunk_message_t * ) ] ;
2020-12-13 22:19:40 +01:00
typedef struct snapcast_sync_task_cfg_s {
2020-12-20 00:41:32 +01:00
audio_element_handle_t * p_raw_stream_writer ;
2020-12-13 22:19:40 +01:00
int64_t outputBufferDacTime_us ;
int64_t buffer_us ;
} snapcast_sync_task_cfg_t ;
2020-12-20 00:41:32 +01:00
typedef struct http_task_cfg_s {
audio_element_handle_t * p_raw_stream_writer_to_decoder ;
audio_element_handle_t * p_raw_stream_writer_to_i2s ;
} http_task_cfg_t ;
2021-03-06 07:23:44 +01:00
SemaphoreHandle_t timeSyncSemaphoreHandle = NULL ;
2021-02-12 14:54:08 +01:00
SemaphoreHandle_t timer0_syncSampleSemaphoreHandle = NULL ;
2021-04-23 08:46:23 +02:00
SemaphoreHandle_t latencyBufSemaphoreHandle = NULL ;
2020-12-13 22:19:40 +01:00
2021-05-16 09:18:27 +02:00
# define MEDIAN_FILTER_LONG_BUF_LEN 299
2021-04-25 21:14:46 +02:00
2021-04-23 08:46:23 +02:00
uint8_t latencyBufCnt = 0 ;
static int8_t latencyBuffFull = 0 ;
2021-04-25 21:14:46 +02:00
static sMedianFilter_t latencyMedianFilterLong ;
static sMedianNode_t latencyMedianLongBuffer [ MEDIAN_FILTER_LONG_BUF_LEN ] ;
2021-04-23 08:46:23 +02:00
static int64_t latencyToServer = 0 ;
//buffer_.setSize(500);
// shortBuffer_.setSize(100);
// miniBuffer_.setSize(20);
2021-05-11 05:44:56 +02:00
# define SHORT_BUFFER_LEN 99
2021-04-23 08:46:23 +02:00
int64_t short_buffer [ SHORT_BUFFER_LEN ] ;
int64_t short_buffer_median [ SHORT_BUFFER_LEN ] ;
static sMedianFilter_t shortMedianFilter ;
static sMedianNode_t shortMedianBuffer [ SHORT_BUFFER_LEN ] ;
static int8_t currentDir = 0 ; //!< current apll direction, see apll_adjust()
2021-04-23 06:31:32 +02:00
2020-12-13 22:19:40 +01:00
uint32_t buffer_ms = 400 ;
2020-06-05 00:23:57 +02:00
uint8_t muteCH [ 4 ] = { 0 } ;
2020-12-13 22:19:40 +01:00
audio_board_handle_t board_handle ;
2020-04-23 00:15:11 +02:00
/* Constants that aren't configurable in menuconfig */
2021-05-03 06:39:30 +02:00
# define HOST "192.168.8.1"
2020-04-23 00:15:11 +02:00
# define PORT 1704
2020-12-22 20:47:33 +01:00
# define BUFF_LEN 10000
2020-12-13 22:19:40 +01:00
unsigned int addr ;
uint32_t port = 0 ;
2020-04-23 00:15:11 +02:00
/* FreeRTOS event group to signal when we are connected & ready to make a request */
//static EventGroupHandle_t wifi_event_group;
/* The event group allows multiple bits for each event,
but we only care about one event - are we connected
to the AP with an IP ? */
static char buff [ BUFF_LEN ] ;
//static audio_element_handle_t snapcast_stream;
static char mac_address [ 18 ] ;
2021-05-03 06:39:30 +02:00
# define MY_SSID "OpenWrt"
# define MY_WPA2_PSK ""
2020-04-23 00:15:11 +02:00
static EventGroupHandle_t s_wifi_event_group ;
2021-04-25 21:14:46 +02:00
const int WIFI_CONNECTED_EVENT = BIT0 ;
const int WIFI_FAIL_EVENT = BIT1 ;
2020-04-23 00:15:11 +02:00
static int s_retry_num = 0 ;
2021-04-23 22:33:05 +02:00
// Event handler for catching system events
static void event_handler ( void * arg , esp_event_base_t event_base , int event_id , void * event_data ) {
if ( event_base = = WIFI_PROV_EVENT ) {
switch ( event_id ) {
case WIFI_PROV_START :
ESP_LOGI ( TAG , " Provisioning started " ) ;
break ;
case WIFI_PROV_CRED_RECV : {
wifi_sta_config_t * wifi_sta_cfg = ( wifi_sta_config_t * ) event_data ;
ESP_LOGI ( TAG , " Received Wi-Fi credentials "
" \n \t SSID : %s \n \t Password : %s " ,
( const char * ) wifi_sta_cfg - > ssid ,
( const char * ) wifi_sta_cfg - > password ) ;
break ;
}
case WIFI_PROV_CRED_FAIL : {
wifi_prov_sta_fail_reason_t * reason = ( wifi_prov_sta_fail_reason_t * ) event_data ;
ESP_LOGE ( TAG , " Provisioning failed! \n \t Reason : %s "
" \n \t Please reset to factory and retry provisioning " ,
( * reason = = WIFI_PROV_STA_AUTH_ERROR ) ?
" Wi-Fi station authentication failed " : " Wi-Fi access-point not found " ) ;
break ;
}
case WIFI_PROV_CRED_SUCCESS :
ESP_LOGI ( TAG , " Provisioning successful " ) ;
break ;
case WIFI_PROV_END :
/* De-initialize manager once provisioning is finished */
wifi_prov_mgr_deinit ( ) ;
break ;
default :
break ;
2020-04-23 00:15:11 +02:00
}
2021-04-23 22:33:05 +02:00
}
else if ( event_base = = WIFI_EVENT & & event_id = = WIFI_EVENT_STA_START ) {
esp_wifi_connect ( ) ;
2020-12-20 00:41:32 +01:00
}
else if ( event_base = = IP_EVENT & & event_id = = IP_EVENT_STA_GOT_IP ) {
2020-04-23 00:15:11 +02:00
ip_event_got_ip_t * event = ( ip_event_got_ip_t * ) event_data ;
2021-04-23 22:33:05 +02:00
ESP_LOGI ( TAG , " Connected with IP Address: " IPSTR , IP2STR ( & event - > ip_info . ip ) ) ;
/* Signal main application to continue execution */
xEventGroupSetBits ( s_wifi_event_group , WIFI_CONNECTED_EVENT ) ;
}
else if ( event_base = = WIFI_EVENT & & event_id = = WIFI_EVENT_STA_DISCONNECTED ) {
ESP_LOGI ( TAG , " Disconnected. Connecting to the AP again... " ) ;
esp_wifi_connect ( ) ;
2020-04-23 00:15:11 +02:00
}
}
2021-04-23 22:33:05 +02:00
static void get_device_service_name ( char * service_name , size_t max )
{
uint8_t eth_mac [ 6 ] ;
const char * ssid_prefix = " PROV_ " ;
esp_wifi_get_mac ( WIFI_IF_STA , eth_mac ) ;
snprintf ( service_name , max , " %s%02X%02X%02X " ,
ssid_prefix , eth_mac [ 3 ] , eth_mac [ 4 ] , eth_mac [ 5 ] ) ;
}
# if CONFIG_USE_WIFI_PROVISIONING == 1
/**
*
*/
static void wifi_init_sta ( void ) {
2021-05-03 06:39:30 +02:00
// Start Wi-Fi in station mode
2021-04-23 22:33:05 +02:00
ESP_ERROR_CHECK ( esp_wifi_set_mode ( WIFI_MODE_STA ) ) ;
ESP_ERROR_CHECK ( esp_wifi_start ( ) ) ;
2020-04-23 00:15:11 +02:00
2021-04-23 22:33:05 +02:00
// Waiting until either the connection is established (WIFI_CONNECTED_EVENT) or connection failed for the maximum
// number of re-tries (WIFI_FAIL_EVENT). The bits are set by event_handler() (see above)
EventBits_t bits = xEventGroupWaitBits ( s_wifi_event_group ,
WIFI_CONNECTED_EVENT | WIFI_FAIL_EVENT ,
pdFALSE ,
pdFALSE ,
portMAX_DELAY ) ;
// xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually happened.
if ( bits & WIFI_CONNECTED_EVENT ) {
ESP_LOGI ( TAG , " connected to ap " ) ;
}
else if ( bits & WIFI_FAIL_EVENT ) {
ESP_LOGI ( TAG , " Failed to connect to AP ... " ) ;
}
else {
ESP_LOGE ( TAG , " UNEXPECTED EVENT " ) ;
}
}
/**
*
*/
void wifi_init_provisioning ( void ) {
// Register our event handler for Wi-Fi, IP and Provisioning related events
ESP_ERROR_CHECK ( esp_event_handler_register ( WIFI_PROV_EVENT , ESP_EVENT_ANY_ID , & event_handler , NULL ) ) ;
ESP_ERROR_CHECK ( esp_event_handler_register ( WIFI_EVENT , ESP_EVENT_ANY_ID , & event_handler , NULL ) ) ;
ESP_ERROR_CHECK ( esp_event_handler_register ( IP_EVENT , IP_EVENT_STA_GOT_IP , & event_handler , NULL ) ) ;
// Initialize Wi-Fi including netif with default config
2020-04-23 00:15:11 +02:00
esp_netif_create_default_wifi_sta ( ) ;
2021-04-23 22:33:05 +02:00
esp_netif_create_default_wifi_ap ( ) ;
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT ( ) ;
ESP_ERROR_CHECK ( esp_wifi_init ( & cfg ) ) ;
2020-04-23 00:15:11 +02:00
2021-04-23 22:33:05 +02:00
// Configuration for the provisioning manager
wifi_prov_mgr_config_t config = {
. scheme = wifi_prov_scheme_softap ,
. scheme_event_handler = WIFI_PROV_EVENT_HANDLER_NONE
} ;
// Initialize provisioning manager with the
// configuration parameters set above
ESP_ERROR_CHECK ( wifi_prov_mgr_init ( config ) ) ;
bool provisioned = false ;
/* Let's find out if the device is provisioned */
ESP_ERROR_CHECK ( wifi_prov_mgr_is_provisioned ( & provisioned ) ) ;
/* If device is not yet provisioned start provisioning service */
if ( ! provisioned ) {
ESP_LOGI ( TAG , " Starting provisioning " ) ;
// Wi-Fi SSID when scheme is wifi_prov_scheme_softap
char service_name [ 12 ] ;
get_device_service_name ( service_name , sizeof ( service_name ) ) ;
/* What is the security level that we want (0 or 1):
* - WIFI_PROV_SECURITY_0 is simply plain text communication .
* - WIFI_PROV_SECURITY_1 is secure communication which consists of secure handshake
* using X25519 key exchange and proof of possession ( pop ) and AES - CTR
* for encryption / decryption of messages .
*/
wifi_prov_security_t security = WIFI_PROV_SECURITY_1 ;
/* Do we want a proof-of-possession (ignored if Security 0 is selected):
* - this should be a string with length > 0
* - NULL if not used
*/
const char * pop = NULL ; //"abcd1234";
/* What is the service key (could be NULL)
* This translates to :
* - Wi - Fi password when scheme is wifi_prov_scheme_softap
* - simply ignored when scheme is wifi_prov_scheme_ble
*/
const char * service_key = " 12345678 " ;
/* An optional endpoint that applications can create if they expect to
* get some additional custom data during provisioning workflow .
* The endpoint name can be anything of your choice .
* This call must be made before starting the provisioning .
*/
//wifi_prov_mgr_endpoint_create("custom-data");
/* Start provisioning service */
ESP_ERROR_CHECK ( wifi_prov_mgr_start_provisioning ( security , pop , service_name , service_key ) ) ;
/* The handler for the optional endpoint created above.
* This call must be made after starting the provisioning , and only if the endpoint
* has already been created above .
*/
//wifi_prov_mgr_endpoint_register("custom-data", custom_prov_data_handler, NULL);
/* Uncomment the following to wait for the provisioning to finish and then release
* the resources of the manager . Since in this case de - initialization is triggered
* by the default event loop handler , we don ' t need to call the following */
// wifi_prov_mgr_wait();
// wifi_prov_mgr_deinit();
}
else {
ESP_LOGI ( TAG , " Already provisioned, starting Wi-Fi STA " ) ;
/* We don't need the manager as device is already provisioned,
* so let ' s release it ' s resources */
wifi_prov_mgr_deinit ( ) ;
/* Start Wi-Fi station */
wifi_init_sta ( ) ;
}
/* Wait for Wi-Fi connection */
xEventGroupWaitBits ( s_wifi_event_group , WIFI_CONNECTED_EVENT , false , true , portMAX_DELAY ) ;
}
/**
*
*/
void wifi_init ( void ) {
wifi_init_provisioning ( ) ;
}
# else
/**
*
*/
void wifi_init ( void ) {
2021-05-03 06:39:30 +02:00
// Register our event handler for Wi-Fi, IP and Provisioning related events
ESP_ERROR_CHECK ( esp_event_handler_register ( WIFI_EVENT , ESP_EVENT_ANY_ID , & event_handler , NULL ) ) ;
ESP_ERROR_CHECK ( esp_event_handler_register ( IP_EVENT , IP_EVENT_STA_GOT_IP , & event_handler , NULL ) ) ;
// Initialize Wi-Fi including netif with default config
esp_netif_create_default_wifi_sta ( ) ;
2020-04-23 00:15:11 +02:00
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT ( ) ;
ESP_ERROR_CHECK ( esp_wifi_init ( & cfg ) ) ;
2021-05-03 06:39:30 +02:00
// wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
// ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
2020-04-23 00:15:11 +02:00
wifi_config_t wifi_config = {
. sta = {
2021-04-23 22:33:05 +02:00
. ssid = MY_SSID ,
. password = MY_WPA2_PSK ,
2020-12-13 22:19:40 +01:00
. bssid_set = false
2020-04-23 00:15:11 +02:00
} ,
} ;
2021-04-23 22:33:05 +02:00
2020-04-23 00:15:11 +02:00
ESP_ERROR_CHECK ( esp_wifi_set_mode ( WIFI_MODE_STA ) ) ;
ESP_ERROR_CHECK ( esp_wifi_set_config ( ESP_IF_WIFI_STA , & wifi_config ) ) ;
ESP_ERROR_CHECK ( esp_wifi_start ( ) ) ;
ESP_LOGI ( TAG , " wifi_init_sta finished. " ) ;
2021-04-23 22:33:05 +02:00
// Waiting until either the connection is established (WIFI_CONNECTED_EVENT) or connection failed for the maximum
// number of re-tries (WIFI_FAIL_EVENT). The bits are set by event_handler() (see above)
2021-02-12 14:54:08 +01:00
EventBits_t bits = xEventGroupWaitBits ( s_wifi_event_group ,
2021-04-23 22:33:05 +02:00
WIFI_CONNECTED_EVENT | WIFI_FAIL_EVENT ,
2021-02-12 14:54:08 +01:00
pdFALSE ,
pdFALSE ,
portMAX_DELAY ) ;
2020-04-23 00:15:11 +02:00
2021-04-23 22:33:05 +02:00
// xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually happened.
if ( bits & WIFI_CONNECTED_EVENT ) {
2020-12-13 22:19:40 +01:00
ESP_LOGI ( TAG , " connected to ap " ) ;
2021-04-23 22:33:05 +02:00
} else if ( bits & WIFI_FAIL_EVENT ) {
2020-12-13 22:19:40 +01:00
ESP_LOGI ( TAG , " Failed to connect to AP ... " ) ;
2020-04-23 00:15:11 +02:00
} else {
ESP_LOGE ( TAG , " UNEXPECTED EVENT " ) ;
}
//ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler));
//ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler));
//vEventGroupDelete(s_wifi_event_group);
}
2021-04-23 22:33:05 +02:00
# endif
2020-04-23 00:15:11 +02:00
2020-05-31 00:55:20 +02:00
static const char * if_str [ ] = { " STA " , " AP " , " ETH " , " MAX " } ;
static const char * ip_protocol_str [ ] = { " V4 " , " V6 " , " MAX " } ;
void mdns_print_results ( mdns_result_t * results ) {
mdns_result_t * r = results ;
mdns_ip_addr_t * a = NULL ;
int i = 1 , t ;
while ( r ) {
printf ( " %d: Interface: %s, Type: %s \n " , i + + , if_str [ r - > tcpip_if ] , ip_protocol_str [ r - > ip_protocol ] ) ;
if ( r - > instance_name ) {
printf ( " PTR : %s \n " , r - > instance_name ) ;
}
if ( r - > hostname ) {
printf ( " SRV : %s.local:%u \n " , r - > hostname , r - > port ) ;
}
if ( r - > txt_count ) {
printf ( " TXT : [%u] " , r - > txt_count ) ;
for ( t = 0 ; t < r - > txt_count ; t + + ) {
printf ( " %s=%s; " , r - > txt [ t ] . key , r - > txt [ t ] . value ) ;
}
printf ( " \n " ) ;
}
a = r - > addr ;
while ( a ) {
if ( a - > addr . type = = IPADDR_TYPE_V6 ) {
printf ( " AAAA: " IPV6STR " \n " , IPV62STR ( a - > addr . u_addr . ip6 ) ) ;
} else {
printf ( " A : " IPSTR " \n " , IP2STR ( & ( a - > addr . u_addr . ip4 ) ) ) ;
}
a = a - > next ;
}
r = r - > next ;
}
}
2020-12-20 00:41:32 +01:00
void find_mdns_service ( const char * service_name , const char * proto ) {
2020-05-31 00:55:20 +02:00
ESP_LOGI ( TAG , " Query PTR: %s.%s.local " , service_name , proto ) ;
mdns_result_t * r = NULL ;
esp_err_t err = mdns_query_ptr ( service_name , proto , 3000 , 20 , & r ) ;
if ( err ) {
ESP_LOGE ( TAG , " Query Failed " ) ;
return ;
}
if ( ! r ) {
ESP_LOGW ( TAG , " No results found! " ) ;
return ;
}
if ( r - > instance_name ) {
printf ( " PTR : %s \n " , r - > instance_name ) ;
}
if ( r - > hostname ) {
printf ( " SRV : %s.local:%u \n " , r - > hostname , r - > port ) ;
port = r - > port ;
}
mdns_query_results_free ( r ) ;
}
2020-12-13 22:19:40 +01:00
/**
*
*/
2021-04-23 08:46:23 +02:00
int8_t reset_latency_buffer ( void ) {
2021-04-23 06:31:32 +02:00
// init diff buff median filter
2021-04-25 21:14:46 +02:00
latencyMedianFilterLong . numNodes = MEDIAN_FILTER_LONG_BUF_LEN ;
latencyMedianFilterLong . medianBuffer = latencyMedianLongBuffer ;
if ( MEDIANFILTER_Init ( & latencyMedianFilterLong ) < 0 ) {
ESP_LOGE ( TAG , " reset_diff_buffer: couldn't init median filter long. STOP " ) ;
return - 2 ;
}
2021-05-24 15:47:24 +02:00
/*
2021-04-25 21:14:46 +02:00
latencyMedianFilterMini . numNodes = MEDIAN_FILTER_MINI_BUF_LEN ;
latencyMedianFilterMini . medianBuffer = latencyMedianMiniBuffer ;
if ( MEDIANFILTER_Init ( & latencyMedianFilterMini ) < 0 ) {
ESP_LOGE ( TAG , " reset_diff_buffer: couldn't init median filter mini. STOP " ) ;
return - 2 ;
}
latencyMedianFilterShort . numNodes = MEDIAN_FILTER_SHORT_BUF_LEN ;
latencyMedianFilterShort . medianBuffer = latencyMedianShortBuffer ;
if ( MEDIANFILTER_Init ( & latencyMedianFilterShort ) < 0 ) {
ESP_LOGE ( TAG , " reset_diff_buffer: couldn't init median filter short. STOP " ) ;
2021-04-23 06:31:32 +02:00
return - 2 ;
}
2021-05-24 15:47:24 +02:00
*/
2021-04-23 06:31:32 +02:00
2021-04-23 08:46:23 +02:00
if ( latencyBufSemaphoreHandle = = NULL ) {
ESP_LOGE ( TAG , " reset_diff_buffer: latencyBufSemaphoreHandle == NULL " ) ;
2021-04-23 06:31:32 +02:00
2021-04-23 08:46:23 +02:00
return - 2 ;
2021-04-23 06:31:32 +02:00
}
2021-04-23 08:46:23 +02:00
if ( xSemaphoreTake ( latencyBufSemaphoreHandle , portMAX_DELAY ) = = pdTRUE ) {
latencyBufCnt = 0 ;
latencyBuffFull = false ;
latencyToServer = 0 ;
2021-03-06 07:23:44 +01:00
2021-04-23 08:46:23 +02:00
xSemaphoreGive ( latencyBufSemaphoreHandle ) ;
2021-03-06 07:23:44 +01:00
}
else {
2021-04-23 08:46:23 +02:00
ESP_LOGW ( TAG , " reset_diff_buffer: can't take semaphore " ) ;
2021-03-06 07:23:44 +01:00
2021-04-23 08:46:23 +02:00
return - 1 ;
}
2021-03-06 07:23:44 +01:00
return 0 ;
2020-12-13 22:19:40 +01:00
}
2021-03-06 07:23:44 +01:00
/**
*
*/
2021-04-25 21:14:46 +02:00
int8_t latency_buffer_full ( void ) {
2021-02-25 23:54:59 +01:00
int8_t tmp ;
2021-04-23 08:46:23 +02:00
if ( latencyBufSemaphoreHandle = = NULL ) {
2021-04-25 21:14:46 +02:00
ESP_LOGE ( TAG , " latency_buffer_full: latencyBufSemaphoreHandle == NULL " ) ;
2021-04-23 08:46:23 +02:00
return - 2 ;
}
if ( xSemaphoreTake ( latencyBufSemaphoreHandle , 0 ) = = pdFALSE ) {
2021-05-03 06:39:30 +02:00
ESP_LOGW ( TAG , " latency_buffer_full: can't take semaphore " ) ;
2021-02-25 23:54:59 +01:00
return - 1 ;
}
2021-04-23 08:46:23 +02:00
tmp = latencyBuffFull ;
2021-02-25 23:54:59 +01:00
2021-04-23 08:46:23 +02:00
xSemaphoreGive ( latencyBufSemaphoreHandle ) ;
2021-02-25 23:54:59 +01:00
return tmp ;
}
2020-12-13 22:19:40 +01:00
/**
*
*/
2021-04-23 06:31:32 +02:00
int8_t get_diff_to_server ( int64_t * tDiff ) {
2021-04-23 08:46:23 +02:00
static int64_t lastDiff = 0 ;
2021-04-23 06:31:32 +02:00
2021-04-23 08:46:23 +02:00
if ( latencyBufSemaphoreHandle = = NULL ) {
ESP_LOGE ( TAG , " get_diff_to_server: latencyBufSemaphoreHandle == NULL " ) ;
2021-04-23 06:31:32 +02:00
2021-04-23 08:46:23 +02:00
return - 2 ;
2021-04-23 06:31:32 +02:00
}
2021-04-23 08:46:23 +02:00
if ( xSemaphoreTake ( latencyBufSemaphoreHandle , 0 ) = = pdFALSE ) {
2021-04-23 06:31:32 +02:00
* tDiff = lastDiff ;
//ESP_LOGW(TAG, "get_diff_to_server: can't take semaphore. Old diff retreived");
2021-04-23 08:46:23 +02:00
return - 1 ;
2021-04-23 06:31:32 +02:00
}
2021-04-23 08:46:23 +02:00
* tDiff = latencyToServer ;
lastDiff = latencyToServer ; // store value, so we can return a value if semaphore couldn't be taken
2021-04-23 06:31:32 +02:00
2021-04-23 08:46:23 +02:00
xSemaphoreGive ( latencyBufSemaphoreHandle ) ;
2021-04-23 06:31:32 +02:00
return 0 ;
}
2020-12-13 22:19:40 +01:00
/**
*
*/
2021-04-23 06:31:32 +02:00
int8_t server_now ( int64_t * sNow ) {
struct timeval now ;
2021-04-23 08:46:23 +02:00
int64_t diff ;
2021-04-23 06:31:32 +02:00
// get current time
if ( gettimeofday ( & now , NULL ) ) {
ESP_LOGE ( TAG , " server_now: Failed to get time of day " ) ;
return - 1 ;
}
if ( get_diff_to_server ( & diff ) = = - 1 ) {
2021-04-23 22:33:05 +02:00
ESP_LOGW ( TAG , " server_now: can't get current diff to server. Retrieved old one " ) ;
2021-04-23 06:31:32 +02:00
}
if ( diff = = 0 ) {
//ESP_LOGW(TAG, "server_now: diff to server not initialized yet");
return - 1 ;
}
2021-04-23 08:46:23 +02:00
* sNow = ( ( int64_t ) now . tv_sec * 1000000LL + ( int64_t ) now . tv_usec ) + diff ;
2021-04-23 06:31:32 +02:00
// ESP_LOGI(TAG, "now: %lldus", (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec);
2021-04-23 08:46:23 +02:00
// ESP_LOGI(TAG, "diff: %lldus", diff);
// ESP_LOGI(TAG, "serverNow: %lldus", *snow);
2020-12-13 22:19:40 +01:00
2021-04-23 06:31:32 +02:00
return 0 ;
}
2020-12-13 22:19:40 +01:00
2021-02-12 14:54:08 +01:00
/*
* Timer group0 ISR handler
*
* Note :
* We don ' t call the timer API here because they are not declared with IRAM_ATTR .
* If we ' re okay with the timer irq not being serviced while SPI flash cache is disabled ,
* we can allocate this interrupt without the ESP_INTR_FLAG_IRAM flag and use the normal API .
*/
void IRAM_ATTR timer_group0_isr ( void * para ) {
timer_spinlock_take ( TIMER_GROUP_0 ) ;
BaseType_t xHigherPriorityTaskWoken = pdFALSE ;
// Retrieve the interrupt status and the counter value
// from the timer that reported the interrupt
uint32_t timer_intr = timer_group_get_intr_status_in_isr ( TIMER_GROUP_0 ) ;
// Clear the interrupt
// and update the alarm time for the timer with without reload
if ( timer_intr & TIMER_INTR_T1 ) {
timer_group_clr_intr_status_in_isr ( TIMER_GROUP_0 , TIMER_1 ) ;
// Notify the task in the task's notification value.
xTaskNotifyFromISR ( syncTaskHandle ,
2021-05-23 21:36:06 +02:00
0 ,
eNoAction ,
2021-02-12 14:54:08 +01:00
& xHigherPriorityTaskWoken ) ;
}
timer_spinlock_give ( TIMER_GROUP_0 ) ;
if ( xHigherPriorityTaskWoken ) {
portYIELD_FROM_ISR ( ) ;
}
}
2021-05-23 21:36:06 +02:00
static void tg0_timer_deinit ( void ) {
timer_deinit ( TIMER_GROUP_0 , TIMER_1 ) ;
}
2021-02-12 14:54:08 +01:00
/*
*
*/
static void tg0_timer_init ( void ) {
// Select and initialize basic parameters of the timer
timer_config_t config = {
2021-02-25 23:54:59 +01:00
//.divider = 8, // 100ns ticks
. divider = 80 , // 1µs ticks
2021-02-12 14:54:08 +01:00
. counter_dir = TIMER_COUNT_UP ,
. counter_en = TIMER_PAUSE ,
. alarm_en = TIMER_ALARM_EN ,
. auto_reload = TIMER_AUTORELOAD_DIS ,
} ; // default clock source is APB
timer_init ( TIMER_GROUP_0 , TIMER_1 , & config ) ;
// Configure the alarm value and the interrupt on alarm.
timer_set_alarm_value ( TIMER_GROUP_0 , TIMER_1 , 0 ) ;
timer_enable_intr ( TIMER_GROUP_0 , TIMER_1 ) ;
//timer_isr_register(TIMER_GROUP_0, TIMER_1, timer_group0_isr, NULL, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3, NULL);
timer_isr_register ( TIMER_GROUP_0 , TIMER_1 , timer_group0_isr , NULL , ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1 , NULL ) ;
}
2021-04-23 08:46:23 +02:00
/**
*
*/
2021-02-12 14:54:08 +01:00
static void tg0_timer1_start ( uint64_t alarm_value ) {
timer_pause ( TIMER_GROUP_0 , TIMER_1 ) ;
timer_set_counter_value ( TIMER_GROUP_0 , TIMER_1 , 0 ) ;
timer_set_alarm_value ( TIMER_GROUP_0 , TIMER_1 , alarm_value ) ;
timer_set_alarm ( TIMER_GROUP_0 , TIMER_1 , TIMER_ALARM_EN ) ;
timer_start ( TIMER_GROUP_0 , TIMER_1 ) ;
// ESP_LOGI(TAG, "started age timer");
}
2021-04-23 08:46:23 +02:00
# if COLLECT_RUNTIME_STATS == 1
2021-02-12 14:54:08 +01:00
# define STATS_TASK_PRIO 3
# define STATS_TICKS pdMS_TO_TICKS(5000)
# define ARRAY_SIZE_OFFSET 5 //Increase this if print_real_time_stats returns ESP_ERR_INVALID_SIZE
//static char task_names[15][configMAX_TASK_NAME_LEN];
/**
* @ brief Function to print the CPU usage of tasks over a given duration .
*
* This function will measure and print the CPU usage of tasks over a specified
* number of ticks ( i . e . real time stats ) . This is implemented by simply calling
* uxTaskGetSystemState ( ) twice separated by a delay , then calculating the
* differences of task run times before and after the delay .
*
* @ note If any tasks are added or removed during the delay , the stats of
* those tasks will not be printed .
* @ note This function should be called from a high priority task to minimize
* inaccuracies with delays .
* @ note When running in dual core mode , each core will correspond to 50 % of
* the run time .
*
* @ param xTicksToWait Period of stats measurement
*
* @ return
* - ESP_OK Success
* - ESP_ERR_NO_MEM Insufficient memory to allocated internal arrays
* - ESP_ERR_INVALID_SIZE Insufficient array size for uxTaskGetSystemState . Trying increasing ARRAY_SIZE_OFFSET
* - ESP_ERR_INVALID_STATE Delay duration too short
*/
static esp_err_t print_real_time_stats ( TickType_t xTicksToWait )
{
TaskStatus_t * start_array = NULL , * end_array = NULL ;
UBaseType_t start_array_size , end_array_size ;
uint32_t start_run_time , end_run_time ;
esp_err_t ret ;
//Allocate array to store current task states
start_array_size = uxTaskGetNumberOfTasks ( ) + ARRAY_SIZE_OFFSET ;
start_array = malloc ( sizeof ( TaskStatus_t ) * start_array_size ) ;
if ( start_array = = NULL ) {
ret = ESP_ERR_NO_MEM ;
goto exit ;
}
//Get current task states
start_array_size = uxTaskGetSystemState ( start_array , start_array_size , & start_run_time ) ;
if ( start_array_size = = 0 ) {
ret = ESP_ERR_INVALID_SIZE ;
goto exit ;
}
vTaskDelay ( xTicksToWait ) ;
//Allocate array to store tasks states post delay
end_array_size = uxTaskGetNumberOfTasks ( ) + ARRAY_SIZE_OFFSET ;
end_array = malloc ( sizeof ( TaskStatus_t ) * end_array_size ) ;
if ( end_array = = NULL ) {
ret = ESP_ERR_NO_MEM ;
goto exit ;
}
//Get post delay task states
end_array_size = uxTaskGetSystemState ( end_array , end_array_size , & end_run_time ) ;
if ( end_array_size = = 0 ) {
ret = ESP_ERR_INVALID_SIZE ;
goto exit ;
}
//Calculate total_elapsed_time in units of run time stats clock period.
uint32_t total_elapsed_time = ( end_run_time - start_run_time ) ;
if ( total_elapsed_time = = 0 ) {
ret = ESP_ERR_INVALID_STATE ;
goto exit ;
}
printf ( " | Task | Run Time | Percentage \n " ) ;
//Match each task in start_array to those in the end_array
for ( int i = 0 ; i < start_array_size ; i + + ) {
int k = - 1 ;
for ( int j = 0 ; j < end_array_size ; j + + ) {
if ( start_array [ i ] . xHandle = = end_array [ j ] . xHandle ) {
k = j ;
//Mark that task have been matched by overwriting their handles
start_array [ i ] . xHandle = NULL ;
end_array [ j ] . xHandle = NULL ;
break ;
}
}
//Check if matching task found
if ( k > = 0 ) {
uint32_t task_elapsed_time = end_array [ k ] . ulRunTimeCounter - start_array [ i ] . ulRunTimeCounter ;
uint32_t percentage_time = ( task_elapsed_time * 100UL ) / ( total_elapsed_time * portNUM_PROCESSORS ) ;
printf ( " | %s | %d | %d%% \n " , start_array [ i ] . pcTaskName , task_elapsed_time , percentage_time ) ;
}
}
//Print unmatched tasks
for ( int i = 0 ; i < start_array_size ; i + + ) {
if ( start_array [ i ] . xHandle ! = NULL ) {
printf ( " | %s | Deleted \n " , start_array [ i ] . pcTaskName ) ;
}
}
for ( int i = 0 ; i < end_array_size ; i + + ) {
if ( end_array [ i ] . xHandle ! = NULL ) {
printf ( " | %s | Created \n " , end_array [ i ] . pcTaskName ) ;
}
}
ret = ESP_OK ;
exit : //Common return path
free ( start_array ) ;
free ( end_array ) ;
return ret ;
}
2021-04-23 08:46:23 +02:00
/**
*
*/
2021-02-12 14:54:08 +01:00
static void stats_task ( void * arg ) {
//Print real time stats periodically
while ( 1 ) {
printf ( " \n \n Getting real time stats over %d ticks \n " , STATS_TICKS ) ;
if ( print_real_time_stats ( STATS_TICKS ) = = ESP_OK ) {
printf ( " Real time stats obtained \n " ) ;
} else {
printf ( " Error getting real time stats \n " ) ;
}
vTaskDelay ( pdMS_TO_TICKS ( 1000 ) ) ;
}
}
2021-04-23 08:46:23 +02:00
# endif // COLLECT_RUNTIME_STATS
2021-04-23 06:31:32 +02:00
2021-04-09 13:57:32 +02:00
// void rtc_clk_apll_enable(bool enable, uint32_t sdm0, uint32_t sdm1, uint32_t sdm2, uint32_t o_div);
// apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536)/((o_div + 2) * 2)
// xtal == 40MHz on lyrat v4.3
// I2S bit_clock = rate * (number of channels) * bits_per_sample
2021-03-06 07:23:44 +01:00
void adjust_apll ( int8_t direction ) {
int sdm0 , sdm1 , sdm2 , o_div ;
2021-04-22 18:15:21 +02:00
int index = 2 ; // 2 for slow adjustment, 0 for fast adjustment
2021-03-06 07:23:44 +01:00
// only change if necessary
if ( currentDir = = direction ) {
return ;
}
if ( direction = = 1 ) {
// speed up
2021-04-22 18:15:21 +02:00
sdm0 = apll_predefine_48k_corr [ index ] [ 2 ] ;
sdm1 = apll_predefine_48k_corr [ index ] [ 3 ] ;
sdm2 = apll_predefine_48k_corr [ index ] [ 4 ] ;
o_div = apll_predefine_48k_corr [ index ] [ 5 ] ;
2021-03-06 07:23:44 +01:00
}
else if ( direction = = - 1 ) {
// slow down
2021-04-22 18:15:21 +02:00
sdm0 = apll_predefine_48k_corr [ index + 1 ] [ 2 ] ;
sdm1 = apll_predefine_48k_corr [ index + 1 ] [ 3 ] ;
sdm2 = apll_predefine_48k_corr [ index + 1 ] [ 4 ] ;
o_div = apll_predefine_48k_corr [ index + 1 ] [ 5 ] ;
2021-03-06 07:23:44 +01:00
}
else {
// reset to normal playback speed
sdm0 = apll_predefine [ 5 ] [ 2 ] ;
sdm1 = apll_predefine [ 5 ] [ 3 ] ;
sdm2 = apll_predefine [ 5 ] [ 4 ] ;
o_div = apll_predefine [ 5 ] [ 5 ] ;
direction = 0 ;
}
rtc_clk_apll_enable ( 1 , sdm0 , sdm1 , sdm2 , o_div ) ;
currentDir = direction ;
}
2021-02-12 14:54:08 +01:00
/**
*
*/
2020-12-13 22:19:40 +01:00
static void snapcast_sync_task ( void * pvParameters ) {
2021-05-23 21:36:06 +02:00
// snapcast_sync_task_cfg_t *taskCfg = (snapcast_sync_task_cfg_t *)pvParameters;
2020-12-20 00:41:32 +01:00
wire_chunk_message_t * chnk = NULL ;
2021-04-23 06:31:32 +02:00
int64_t serverNow = 0 ;
2020-12-13 22:19:40 +01:00
int64_t age ;
BaseType_t ret ;
2021-05-16 09:18:27 +02:00
int64_t chunkDuration_us = WIRE_CHUNK_DURATION_MS * 1000 ;
2021-02-12 14:54:08 +01:00
int64_t sampleDuration_ns = ( 1000000 / 48 ) ; // 16bit, 2ch, 48kHz (in nano seconds)
char * p_payload = NULL ;
2021-02-25 23:54:59 +01:00
size_t size = 0 ;
2021-02-12 14:54:08 +01:00
uint32_t notifiedValue ;
uint64_t timer_val ;
2021-02-15 20:53:31 +01:00
int32_t alarmValSub = 0 ;
int initialSync = 0 ;
2021-04-23 08:46:23 +02:00
int64_t avg = 0 ;
int64_t latencyInitialSync = 0 ;
2021-04-22 18:15:21 +02:00
int dir = 0 ;
2021-05-03 06:39:30 +02:00
i2s_event_t i2sEvent ;
uint32_t i2sDmaBufferCnt = 0 ;
2021-05-11 05:44:56 +02:00
int writtenBytes , bytesAvailable ;
2021-05-23 21:36:06 +02:00
int64_t buffer_ms_local = buffer_ms ;
2020-12-13 22:19:40 +01:00
ESP_LOGI ( TAG , " started sync task " ) ;
2021-05-23 21:36:06 +02:00
// tg0_timer_init(); // initialize initial sync timer
2021-02-12 14:54:08 +01:00
2021-02-15 20:53:31 +01:00
initialSync = 0 ;
2021-05-23 21:36:06 +02:00
currentDir = 1 ; // force adjust_apll to set correct playback speed
2021-04-23 08:46:23 +02:00
adjust_apll ( 0 ) ;
2021-02-15 20:53:31 +01:00
2021-04-23 06:31:32 +02:00
shortMedianFilter . numNodes = SHORT_BUFFER_LEN ;
shortMedianFilter . medianBuffer = shortMedianBuffer ;
if ( MEDIANFILTER_Init ( & shortMedianFilter ) ) {
ESP_LOGE ( TAG , " snapcast_sync_task: couldn't init shortMedianFilter. STOP " ) ;
return ;
}
2020-12-13 22:19:40 +01:00
while ( 1 ) {
2021-05-23 21:36:06 +02:00
// get notification value which holds buffer_ms as communicated by snapserver
xTaskNotifyWait ( pdFALSE , // Don't clear bits on entry.
pdFALSE , // Don't clear bits on exit
& notifiedValue , // Stores the notified value.
0
) ;
buffer_ms_local = ( int64_t ) notifiedValue * 1000LL ;
2020-12-20 00:41:32 +01:00
if ( chnk = = NULL ) {
2021-05-16 11:19:58 +02:00
ret = xQueueReceive ( pcmChunkQueueHandle , & chnk , pdMS_TO_TICKS ( 2000 ) ) ;
if ( ret ! = pdFAIL ) {
// ESP_LOGW(TAG, "got pcm chunk");
2021-02-25 23:54:59 +01:00
}
2020-12-13 22:19:40 +01:00
}
else {
2021-05-03 06:39:30 +02:00
// ESP_LOGW(TAG, "already retrieved chunk needs service");
2020-12-13 22:19:40 +01:00
ret = pdPASS ;
}
2021-05-03 06:39:30 +02:00
if ( ret ! = pdFAIL ) {
2021-05-16 11:19:58 +02:00
if ( server_now ( & serverNow ) > = 0 ) {
2021-04-23 08:46:23 +02:00
age = serverNow -
( ( int64_t ) chnk - > timestamp . sec * 1000000LL + ( int64_t ) chnk - > timestamp . usec ) -
2021-05-23 21:36:06 +02:00
( int64_t ) buffer_ms_local +
2021-05-16 11:19:58 +02:00
( int64_t ) i2sDmaLAtency +
2021-05-23 21:36:06 +02:00
( int64_t ) DAC_OUT_BUFFER_TIME_US ;
2021-04-23 08:46:23 +02:00
}
2021-05-16 11:19:58 +02:00
else {
ESP_LOGW ( TAG , " couldn't get server now " ) ;
if ( chnk ! = NULL ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
vTaskDelay ( pdMS_TO_TICKS ( 1 ) ) ;
continue ;
}
2021-04-22 18:15:21 +02:00
2021-05-23 21:36:06 +02:00
/*
2021-05-03 06:39:30 +02:00
// wait for early time syncs to be ready
int tmp = latency_buffer_full ( ) ;
if ( tmp < = 0 ) {
if ( tmp < 0 ) {
2021-05-23 21:36:06 +02:00
ESP_LOGW ( TAG , " test " ) ;
2021-05-03 06:39:30 +02:00
vTaskDelay ( 1 ) ;
continue ;
}
if ( age > = 0 ) {
2021-05-16 09:18:27 +02:00
if ( chnk ! = NULL ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
2021-05-03 06:39:30 +02:00
}
2021-05-16 09:18:27 +02:00
ESP_LOGW ( TAG , " diff buffer not full " ) ;
2021-05-03 06:39:30 +02:00
vTaskDelay ( pdMS_TO_TICKS ( 10 ) ) ;
continue ;
}
2021-05-23 21:36:06 +02:00
*/
2021-05-03 06:39:30 +02:00
if ( chnk ! = NULL ) {
p_payload = chnk - > payload ;
size = chnk - > size ;
}
2021-04-23 08:46:23 +02:00
if ( age < 0 ) { // get initial sync using hardware timer
if ( initialSync = = 0 ) {
if ( MEDIANFILTER_Init ( & shortMedianFilter ) ) {
ESP_LOGE ( TAG , " snapcast_sync_task: couldn't init shortMedianFilter. STOP " ) ;
return ;
}
2021-04-22 18:15:21 +02:00
2021-05-16 09:18:27 +02:00
/*
ESP_LOGI ( TAG , " age before sync %lld " , age ) ;
// ensure enough time for resync
if ( age > - ( int64_t ) WIRE_CHUNK_DURATION_MS * 1000 * 2 ) {
if ( chnk ! = NULL ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
ESP_LOGI ( TAG , " trying to resync " ) ;
vTaskDelay ( 1 ) ;
continue ;
}
*/
2021-04-23 22:33:05 +02:00
adjust_apll ( 0 ) ; // reset to normal playback speed
2021-05-16 09:18:27 +02:00
i2s_zero_dma_buffer ( i2s_cfg . i2s_port ) ;
i2s_stop ( i2s_cfg . i2s_port ) ;
size_t written ;
if ( i2s_write ( I2S_NUM_0 , p_payload , ( size_t ) size , & written , 0 ) ! = ESP_OK ) {
ESP_LOGE ( TAG , " i2s_playback_task: I2S write error " ) ;
}
size - = written ;
p_payload + = written ;
2021-05-11 05:44:56 +02:00
if ( ( chnk ! = NULL ) & & ( size = = 0 ) ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
2021-05-03 06:39:30 +02:00
}
2021-05-11 05:44:56 +02:00
2021-04-23 08:46:23 +02:00
//tg0_timer1_start((-age * 10) - alarmValSub)); // alarm a little earlier to account for context switch duration from freeRTOS, timer with 100ns ticks
tg0_timer1_start ( - age - alarmValSub ) ; // alarm a little earlier to account for context switch duration from freeRTOS, timer with 1µs ticks
2021-04-22 18:15:21 +02:00
2021-05-11 05:44:56 +02:00
// Wait to be notified of a timer interrupt.
2021-04-22 18:15:21 +02:00
xTaskNotifyWait ( pdFALSE , // Don't clear bits on entry.
2021-05-23 21:36:06 +02:00
pdFALSE , // Don't clear bits on exit.
2021-04-22 18:15:21 +02:00
& notifiedValue , // Stores the notified value.
portMAX_DELAY
) ;
2021-05-16 09:18:27 +02:00
i2s_start ( i2s_cfg . i2s_port ) ;
2021-04-23 08:46:23 +02:00
2021-04-22 18:15:21 +02:00
// get timer value so we can get the real age
timer_get_counter_value ( TIMER_GROUP_0 , TIMER_1 , & timer_val ) ;
timer_pause ( TIMER_GROUP_0 , TIMER_1 ) ;
2021-04-23 08:46:23 +02:00
// get actual age after alarm
//age = ((int64_t)timer_val - (-age) * 10) / 10; // timer with 100ns ticks
age = ( int64_t ) timer_val - ( - age ) ; // timer with 1µs ticks
2021-04-22 18:15:21 +02:00
2021-05-16 09:18:27 +02:00
if ( i2s_write ( I2S_NUM_0 , p_payload , ( size_t ) size , & written , portMAX_DELAY ) ! = ESP_OK ) {
ESP_LOGE ( TAG , " i2s_playback_task: I2S write error " ) ;
}
size - = written ;
p_payload + = written ;
if ( ( chnk ! = NULL ) & & ( size = = 0 ) )
{
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
ret = xQueueReceive ( pcmChunkQueueHandle , & chnk , portMAX_DELAY ) ;
if ( ret ! = pdFAIL ) {
p_payload = chnk - > payload ;
size = chnk - > size ;
if ( i2s_write ( I2S_NUM_0 , p_payload , ( size_t ) size , & written , portMAX_DELAY ) ! = ESP_OK ) {
ESP_LOGE ( TAG , " i2s_playback_task: I2S write error " ) ;
}
size - = written ;
p_payload + = written ;
if ( ( chnk ! = NULL ) & & ( size = = 0 ) ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
}
}
2021-04-22 18:15:21 +02:00
initialSync = 1 ;
2021-05-03 06:39:30 +02:00
ESP_LOGI ( TAG , " initial sync %lldus " , age ) ;
2021-05-11 05:44:56 +02:00
2021-05-03 06:39:30 +02:00
continue ;
2021-04-22 18:15:21 +02:00
}
2021-04-23 08:46:23 +02:00
}
else if ( ( age > 0 ) & & ( initialSync = = 0 ) ) {
2021-05-03 06:39:30 +02:00
if ( chnk ! = NULL ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
2021-04-22 18:15:21 +02:00
2021-04-23 08:46:23 +02:00
int64_t t ;
get_diff_to_server ( & t ) ;
ESP_LOGW ( TAG , " RESYNCING HARD 1 %lldus, %lldus " , age , t ) ;
2021-04-22 18:15:21 +02:00
2021-04-23 08:46:23 +02:00
dir = 0 ;
2021-04-22 18:15:21 +02:00
2021-04-23 08:46:23 +02:00
initialSync = 0 ;
alarmValSub = 0 ;
2021-04-22 18:15:21 +02:00
2021-04-23 08:46:23 +02:00
continue ;
}
2021-04-22 18:15:21 +02:00
2021-04-23 08:46:23 +02:00
if ( initialSync = = 1 ) {
2021-05-03 06:39:30 +02:00
const uint8_t enableControlLoop = 1 ;
2021-05-16 09:18:27 +02:00
const int64_t age_expect = - chunkDuration_us * 2 ;
const int64_t maxOffset = 100 ; //µs, softsync 1
const int64_t maxOffset_dropSample = 1000 ; //µs, softsync 2
const int64_t hardResyncThreshold = 1500 ; //µs, hard sync
2021-04-22 18:15:21 +02:00
2021-05-03 07:29:36 +02:00
avg = MEDIANFILTER_Insert ( & shortMedianFilter , age + ( - age_expect ) ) ;
if ( MEDIANFILTER_isFull ( & shortMedianFilter ) = = 0 ) {
avg = age + ( - age_expect ) ;
}
2021-05-16 09:18:27 +02:00
else {
// resync hard if we are off too far
if ( ( avg < - hardResyncThreshold ) | | ( avg > hardResyncThreshold ) | | ( initialSync = = 0 ) ) {
if ( chnk ! = NULL ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
2021-04-22 18:15:21 +02:00
2021-05-16 09:18:27 +02:00
int64_t t ;
get_diff_to_server ( & t ) ;
ESP_LOGW ( TAG , " RESYNCING HARD 2 %lldus, %lldus, %lldus " , age , avg , t ) ;
2021-04-22 18:15:21 +02:00
2021-05-16 09:18:27 +02:00
initialSync = 0 ;
alarmValSub = 0 ;
2021-04-22 18:15:21 +02:00
2021-05-16 09:18:27 +02:00
i2sDmaBufferCnt = i2sDmaBufCnt * 1 ; // ensure dma is empty
do {
// wait until DMA queue is empty
ret = xQueueReceive ( i2s_event_queue , & i2sEvent , portMAX_DELAY ) ;
if ( ret ! = pdFAIL ) {
if ( i2sEvent . type = = I2S_EVENT_TX_DONE ) {
ESP_LOGI ( TAG , " I2S_EVENT_TX_DONE, %u " , i2sDmaBufferCnt ) ;
2021-04-22 18:15:21 +02:00
2021-05-16 09:18:27 +02:00
i2sDmaBufferCnt - - ;
}
}
} while ( i2sDmaBufferCnt > 0 ) ;
2021-04-22 18:15:21 +02:00
2021-05-16 09:18:27 +02:00
continue ;
}
2021-05-03 06:39:30 +02:00
}
2021-04-23 08:46:23 +02:00
int samples = 1 ;
int sampleSize = 4 ;
int ageDiff = 0 ;
2021-05-16 09:18:27 +02:00
size_t written ;
2021-04-23 08:46:23 +02:00
2021-05-03 06:39:30 +02:00
if ( enableControlLoop = = 1 ) {
2021-05-11 05:44:56 +02:00
if ( avg < - maxOffset ) { // we are early
2021-04-23 08:46:23 +02:00
dir = - 1 ;
2021-05-16 09:18:27 +02:00
/*
//if ( MEDIANFILTER_isFull(&shortMedianFilter))
{
if ( avg < - maxOffset_dropSample ) {
//ageDiff = (int)(age_expect - avg);
ageDiff = - ( int ) avg ;
samples = ageDiff / ( sampleDuration_ns / 1000 ) ;
if ( samples > 4 ) {
samples = 4 ;
}
ESP_LOGI ( TAG , " insert %d samples " , samples ) ;
// TODO: clean insert at periodic positions, so stretching won't be audible.
char * newBuf = NULL ;
newBuf = ( char * ) heap_caps_malloc ( sizeof ( char ) * ( size + samples * sampleSize ) , MALLOC_CAP_SPIRAM ) ;
memcpy ( newBuf , p_payload , size ) ;
free ( p_payload ) ;
p_payload = newBuf ;
memcpy ( & p_payload [ size ] , & p_payload [ size - 1 - samples * sampleSize ] , samples * sampleSize ) ;
size + = samples * sampleSize ;
chnk - > payload = p_payload ;
chnk - > size = size ;
}
}
*/
2021-04-22 18:15:21 +02:00
}
2021-05-03 06:39:30 +02:00
else if ( ( avg > = - maxOffset ) & & ( avg < = maxOffset ) ) {
2021-04-23 08:46:23 +02:00
dir = 0 ;
}
2021-05-11 05:44:56 +02:00
else if ( avg > maxOffset ) { // we are late
2021-04-23 08:46:23 +02:00
dir = 1 ;
2021-05-16 09:18:27 +02:00
/*
//if ( MEDIANFILTER_isFull(&shortMedianFilter))
{
if ( avg > maxOffset_dropSample ) {
//ageDiff = (int)(avg - age_expect);
ageDiff = ( int ) avg ;
samples = ageDiff / ( sampleDuration_ns / 1000 ) ;
if ( samples > 4 ) {
samples = 4 ;
}
if ( size > = samples * sampleSize ) {
// drop samples
p_payload + = samples * sampleSize ;
size - = samples * sampleSize ;
ESP_LOGI ( TAG , " drop %d samples " , samples ) ;
}
}
}
*/
2021-04-22 18:15:21 +02:00
}
2021-04-23 08:46:23 +02:00
adjust_apll ( dir ) ;
}
2021-05-11 05:44:56 +02:00
2021-05-16 09:18:27 +02:00
if ( i2s_write ( I2S_NUM_0 , p_payload , ( size_t ) size , & written , portMAX_DELAY ) ! = ESP_OK ) {
ESP_LOGE ( TAG , " i2s_playback_task: I2S write error " ) ;
}
size - = written ;
p_payload + = written ;
2021-05-11 05:44:56 +02:00
if ( ( chnk ! = NULL ) & & ( size = = 0 ) ) {
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
2021-04-22 18:15:21 +02:00
}
2021-04-23 08:46:23 +02:00
int64_t t ;
get_diff_to_server ( & t ) ;
2021-05-03 06:39:30 +02:00
ESP_LOGI ( TAG , " %d: %lldus, %lldus %lldus " , dir , age , avg , t ) ;
2021-04-22 18:15:21 +02:00
2021-05-11 05:44:56 +02:00
if ( ( chnk ! = NULL ) & & ( size = = 0 ) ) {
2021-05-03 06:39:30 +02:00
free ( chnk - > payload ) ;
free ( chnk ) ;
chnk = NULL ;
}
2021-04-22 18:15:21 +02:00
}
else {
2021-04-23 08:46:23 +02:00
int64_t t ;
get_diff_to_server ( & t ) ;
ESP_LOGE ( TAG , " Couldn't get PCM chunk, recv: messages waiting %d, %d, latency %lldus " , uxQueueMessagesWaiting ( pcmChunkQueueHandle ) , uxQueueMessagesWaiting ( timestampQueueHandle ) , t ) ;
dir = 0 ;
2021-04-22 18:15:21 +02:00
2021-02-15 20:53:31 +01:00
initialSync = 0 ;
2021-02-12 14:54:08 +01:00
alarmValSub = 0 ;
2020-12-22 20:47:33 +01:00
}
2020-12-13 22:19:40 +01:00
}
}
2021-03-06 07:23:44 +01:00
/**
*
*/
void time_sync_msg_cb ( void * args ) {
static BaseType_t xHigherPriorityTaskWoken ;
//xSemaphoreGive(timeSyncSemaphoreHandle); // causes kernel panic, which shouldn't happen though? Isn't it called from timer task instead of ISR?
xSemaphoreGiveFromISR ( timeSyncSemaphoreHandle , & xHigherPriorityTaskWoken ) ;
}
2020-12-13 22:19:40 +01:00
2020-12-20 00:41:32 +01:00
2020-12-13 22:19:40 +01:00
/**
*
*/
static void http_get_task ( void * pvParameters ) {
2020-04-23 00:15:11 +02:00
struct sockaddr_in servaddr ;
char * start ;
2021-05-24 15:47:24 +02:00
int sock = - 1 ;
2020-04-23 00:15:11 +02:00
char base_message_serialized [ BASE_MESSAGE_SIZE ] ;
2021-05-23 21:36:06 +02:00
char * hello_message_serialized = NULL ;
2020-04-23 00:15:11 +02:00
int result , size , id_counter ;
2021-02-25 23:54:59 +01:00
struct timeval now , tv1 , tv2 , tv3 ;
2020-04-23 00:15:11 +02:00
time_message_t time_message ;
2020-12-13 22:19:40 +01:00
struct timeval tmpDiffToServer ;
const int64_t outputBufferDacTime_us = DAC_OUT_BUFFER_TIME_US ; // in ms
2021-05-23 21:36:06 +02:00
// snapcast_sync_task_cfg_t snapcastTaskCfg;
2020-12-20 00:41:32 +01:00
struct timeval lastTimeSync = { 0 , 0 } ;
2021-02-12 14:54:08 +01:00
wire_chunk_message_t wire_chunk_message_last = { { 0 , 0 } , 0 , NULL } ;
2021-05-24 15:47:24 +02:00
esp_timer_handle_t timeSyncMessageTimer = NULL ;
2021-03-06 07:23:44 +01:00
const esp_timer_create_args_t tSyncArgs = {
. callback = & time_sync_msg_cb ,
. name = " tSyncMsg "
} ;
2021-05-16 09:18:27 +02:00
int16_t frameSize = 960 ; // 960*2: 20ms, 960*1: 10ms
int16_t * audio = NULL ;
int16_t pcm_size = 120 ;
uint16_t channels = CHANNELS ;
2021-05-24 15:47:24 +02:00
esp_err_t err = 0 ;
codec_header_message_t codec_header_message ;
server_settings_message_t server_settings_message ;
bool received_header = false ;
base_message_t base_message ;
hello_message_t hello_message ;
mdns_result_t * r ;
2020-12-20 00:41:32 +01:00
2020-12-13 22:19:40 +01:00
// create semaphore for time diff buffer to server
2021-04-23 08:46:23 +02:00
latencyBufSemaphoreHandle = xSemaphoreCreateMutex ( ) ;
2020-04-23 00:15:11 +02:00
2021-03-06 07:23:44 +01:00
// create a timer to send time sync messages every x µs
esp_timer_create ( & tSyncArgs , & timeSyncMessageTimer ) ;
timeSyncSemaphoreHandle = xSemaphoreCreateMutex ( ) ;
xSemaphoreGive ( timeSyncSemaphoreHandle ) ;
2020-04-23 00:15:11 +02:00
id_counter = 0 ;
2020-12-13 22:19:40 +01:00
// create snapcast receive buffer
2020-12-20 00:41:32 +01:00
pcmChunkQueueHandle = xQueueCreateStatic ( PCM_CHNK_QUEUE_LENGTH ,
sizeof ( wire_chunk_message_t * ) ,
pcmChunkQueueStorageArea ,
& pcmChunkQueue
) ;
2020-04-24 14:47:43 +02:00
2021-05-24 15:47:24 +02:00
// init diff buff median filter
latencyMedianFilterLong . numNodes = MEDIAN_FILTER_LONG_BUF_LEN ;
latencyMedianFilterLong . medianBuffer = latencyMedianLongBuffer ;
2021-05-23 21:36:06 +02:00
ESP_LOGI ( TAG , " Enable mdns " ) ;
mdns_init ( ) ;
2020-04-23 00:15:11 +02:00
while ( 1 ) {
2021-04-25 21:14:46 +02:00
if ( MEDIANFILTER_Init ( & latencyMedianFilterLong ) < 0 ) {
ESP_LOGE ( TAG , " reset_diff_buffer: couldn't init median filter long. STOP " ) ;
return ;
}
2021-05-24 15:47:24 +02:00
esp_timer_stop ( timeSyncMessageTimer ) ;
2021-04-25 21:14:46 +02:00
2021-05-24 15:47:24 +02:00
xSemaphoreGive ( timeSyncSemaphoreHandle ) ;
2021-04-25 21:14:46 +02:00
2021-05-24 15:47:24 +02:00
if ( opusDecoder ! = NULL ) {
opus_decoder_destroy ( opusDecoder ) ;
opusDecoder = NULL ;
}
2021-04-23 06:31:32 +02:00
2021-05-24 15:47:24 +02:00
reset_latency_buffer ( ) ;
if ( xSemaphoreTake ( latencyBufSemaphoreHandle , pdMS_TO_TICKS ( 1 ) ) = = pdTRUE ) {
latencyBuffFull = false ;
latencyToServer = 0 ;
2021-04-23 06:31:32 +02:00
2021-05-24 15:47:24 +02:00
xSemaphoreGive ( latencyBufSemaphoreHandle ) ;
}
else {
ESP_LOGW ( TAG , " couldn't reset latency " ) ;
}
2020-04-23 00:15:11 +02:00
2020-12-13 22:19:40 +01:00
// Find snapcast server
2020-05-31 00:55:20 +02:00
// Connect to first snapcast server found
2021-05-24 15:47:24 +02:00
r = NULL ;
err = 0 ;
2021-04-23 08:46:23 +02:00
while ( ! r | | err ) {
ESP_LOGI ( TAG , " Lookup snapcast service on network " ) ;
2021-05-24 15:47:24 +02:00
esp_err_t err = mdns_query_ptr ( " _snapcast " , " _tcp " , 3000 , 20 , & r ) ;
if ( err ) {
ESP_LOGE ( TAG , " Query Failed " ) ;
}
if ( ! r ) {
ESP_LOGW ( TAG , " No results found! " ) ;
}
vTaskDelay ( 1000 / portTICK_PERIOD_MS ) ;
2020-12-13 22:19:40 +01:00
}
2021-05-24 15:47:24 +02:00
char serverAddr [ ] = " 192.168.1.2 " ;
ESP_LOGI ( TAG , " Found %s:%d " , inet_ntop ( AF_INET , & ( r - > addr - > addr . u_addr . ip4 . addr ) , serverAddr , sizeof ( serverAddr ) ) , r - > port ) ;
2021-03-06 07:23:44 +01:00
2020-04-23 00:15:11 +02:00
servaddr . sin_family = AF_INET ;
2021-05-24 15:47:24 +02:00
servaddr . sin_addr . s_addr = r - > addr - > addr . u_addr . ip4 . addr ;
2020-05-31 00:55:20 +02:00
servaddr . sin_port = htons ( r - > port ) ;
mdns_query_results_free ( r ) ;
2020-04-23 00:15:11 +02:00
2021-05-24 15:47:24 +02:00
ESP_LOGI ( TAG , " allocate socket " ) ;
sock = socket ( AF_INET , SOCK_STREAM , 0 ) ;
if ( sock < 0 ) {
ESP_LOGE ( TAG , " ... Failed to allocate socket. " ) ;
vTaskDelay ( 1000 / portTICK_PERIOD_MS ) ;
continue ;
}
ESP_LOGI ( TAG , " ... allocated socket %d " , sock ) ;
ESP_LOGI ( TAG , " connect to socket " ) ;
err = connect ( sock , ( struct sockaddr * ) & servaddr , sizeof ( struct sockaddr_in ) ) ;
if ( err < 0 ) {
ESP_LOGE ( TAG , " %s, %d " , strerror ( errno ) , errno ) ;
shutdown ( sock , 2 ) ;
closesocket ( sock ) ;
2020-04-23 00:15:11 +02:00
vTaskDelay ( 4000 / portTICK_PERIOD_MS ) ;
2021-05-24 15:47:24 +02:00
2020-04-23 00:15:11 +02:00
continue ;
}
ESP_LOGI ( TAG , " ... connected " ) ;
result = gettimeofday ( & now , NULL ) ;
if ( result ) {
ESP_LOGI ( TAG , " Failed to gettimeofday \r \n " ) ;
return ;
}
2021-05-24 15:47:24 +02:00
received_header = false ;
// init base mesage
base_message . type = SNAPCAST_MESSAGE_HELLO ;
base_message . id = 0x0000 ;
base_message . refersTo = 0x0000 ;
base_message . sent . sec = now . tv_sec ;
base_message . sent . usec = now . tv_usec ;
base_message . received . sec = 0 ;
base_message . received . usec = 0 ;
base_message . size = 0x00000000 ;
// init hello message
hello_message . mac = mac_address ;
hello_message . hostname = " ESP32-Caster " ;
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 ;
2020-04-23 00:15:11 +02:00
2021-05-23 21:36:06 +02:00
if ( hello_message_serialized = = NULL ) {
hello_message_serialized = hello_message_serialize ( & hello_message , ( size_t * ) & ( base_message . size ) ) ;
if ( ! hello_message_serialized ) {
ESP_LOGE ( TAG , " Failed to serialize hello message \r \b " ) ;
return ;
}
2020-04-23 00:15:11 +02:00
}
result = base_message_serialize (
2021-05-23 21:36:06 +02:00
& base_message ,
base_message_serialized ,
BASE_MESSAGE_SIZE
) ;
2020-04-23 00:15:11 +02:00
if ( result ) {
2021-05-23 21:36:06 +02:00
ESP_LOGE ( TAG , " Failed to serialize base message \r \n " ) ;
2020-04-23 00:15:11 +02:00
return ;
}
2020-12-13 22:19:40 +01:00
2021-05-24 15:47:24 +02:00
result = send ( sock , base_message_serialized , BASE_MESSAGE_SIZE , 0 ) ;
2021-05-23 21:36:06 +02:00
if ( result < 0 ) {
ESP_LOGW ( TAG , " error writing base msg to socket: %s " , strerror ( errno ) ) ;
2021-05-24 15:47:24 +02:00
2021-05-23 21:36:06 +02:00
free ( hello_message_serialized ) ;
hello_message_serialized = NULL ;
2021-05-24 15:47:24 +02:00
shutdown ( sock , 2 ) ;
closesocket ( sock ) ;
2021-05-23 21:36:06 +02:00
continue ;
}
2021-05-24 15:47:24 +02:00
result = send ( sock , hello_message_serialized , base_message . size , 0 ) ;
2021-05-23 21:36:06 +02:00
if ( result < 0 ) {
ESP_LOGW ( TAG , " error writing hello msg to socket: %s " , strerror ( errno ) ) ;
2021-05-24 15:47:24 +02:00
2021-05-23 21:36:06 +02:00
free ( hello_message_serialized ) ;
hello_message_serialized = NULL ;
2021-05-24 15:47:24 +02:00
shutdown ( sock , 2 ) ;
closesocket ( sock ) ;
2021-05-23 21:36:06 +02:00
continue ;
}
2020-04-23 00:15:11 +02:00
free ( hello_message_serialized ) ;
2021-05-23 21:36:06 +02:00
hello_message_serialized = NULL ;
2020-12-13 22:19:40 +01:00
2020-04-23 00:15:11 +02:00
for ( ; ; ) {
size = 0 ;
2020-12-13 22:19:40 +01:00
result = 0 ;
2020-04-23 00:15:11 +02:00
while ( size < BASE_MESSAGE_SIZE ) {
2021-05-24 15:47:24 +02:00
//result = read(sock, &(buff[size]), BASE_MESSAGE_SIZE - size);
result = recv ( sock , & ( buff [ size ] ) , BASE_MESSAGE_SIZE - size , 0 ) ;
2020-04-23 00:15:11 +02:00
if ( result < 0 ) {
2020-12-13 22:19:40 +01:00
break ;
2020-04-23 00:15:11 +02:00
}
size + = result ;
}
2020-12-20 00:41:32 +01:00
if ( result < 0 ) {
if ( errno ! = 0 ) {
2021-05-24 15:47:24 +02:00
ESP_LOGW ( TAG , " 1: %s, %d " , strerror ( errno ) , ( int ) errno ) ;
2020-12-20 00:41:32 +01:00
}
2020-04-23 00:15:11 +02:00
2021-05-24 15:47:24 +02:00
shutdown ( sock , 2 ) ;
closesocket ( sock ) ;
2021-05-23 21:36:06 +02:00
2020-12-13 22:19:40 +01:00
break ; // stop for(;;) will try to reconnect then
2020-04-23 00:15:11 +02:00
}
2020-04-24 14:47:43 +02:00
2020-12-13 22:19:40 +01:00
if ( result > 0 ) {
result = gettimeofday ( & now , NULL ) ;
//ESP_LOGI(TAG, "time of day: %ld %ld", now.tv_sec, now.tv_usec);
if ( result ) {
2021-05-23 21:36:06 +02:00
ESP_LOGW ( TAG , " Failed to gettimeofday " ) ;
2020-12-13 22:19:40 +01:00
continue ;
}
result = base_message_deserialize ( & base_message , buff , size ) ;
if ( result ) {
2021-05-23 21:36:06 +02:00
ESP_LOGW ( TAG , " Failed to read base message: %d " , result ) ;
2020-12-13 22:19:40 +01:00
continue ;
}
base_message . received . usec = now . tv_usec ;
// ESP_LOGI(TAG,"%d %d : %d %d : %d %d",base_message.size, base_message.refersTo,
// base_message.sent.sec,base_message.sent.usec,
// base_message.received.sec,base_message.received.usec);
start = buff ;
size = 0 ;
2020-12-20 00:41:32 +01:00
// TODO: dynamically allocate memory for the next read!!!
// generate an error for now if we try to read more than BUFF_LEN in next lines
if ( base_message . size > BUFF_LEN ) {
ESP_LOGE ( TAG , " base_message.size too big %d " , base_message . size ) ;
return ;
}
2020-12-13 22:19:40 +01:00
while ( size < base_message . size ) {
2020-12-20 00:41:32 +01:00
if ( size > = BUFF_LEN ) {
ESP_LOGE ( TAG , " Index too high " ) ;
return ;
}
2021-05-24 15:47:24 +02:00
result = recv ( sock , & ( buff [ size ] ) , base_message . size - size , 0 ) ;
2020-12-13 22:19:40 +01:00
if ( result < 0 ) {
2021-05-23 21:36:06 +02:00
ESP_LOGW ( TAG , " Failed to read from server: %d " , result ) ;
2020-12-13 22:19:40 +01:00
break ;
}
size + = result ;
}
if ( result < 0 ) {
2020-12-20 00:41:32 +01:00
if ( errno ! = 0 ) {
2021-05-24 15:47:24 +02:00
ESP_LOGI ( TAG , " 2: %s, %d " , strerror ( errno ) , ( int ) errno ) ;
2020-12-20 00:41:32 +01:00
}
2021-05-24 15:47:24 +02:00
shutdown ( sock , 2 ) ;
closesocket ( sock ) ;
2021-05-23 21:36:06 +02:00
2020-12-20 00:41:32 +01:00
break ; // stop for(;;) will try to reconnect then
2020-12-13 22:19:40 +01:00
}
switch ( base_message . type ) {
case SNAPCAST_MESSAGE_CODEC_HEADER :
result = codec_header_message_deserialize ( & codec_header_message , start , size ) ;
if ( result ) {
ESP_LOGI ( TAG , " Failed to read codec header: %d " , result ) ;
return ;
}
size = codec_header_message . size ;
start = codec_header_message . payload ;
2020-12-20 00:41:32 +01:00
//ESP_LOGI(TAG, "Received codec header message with size %d", codec_header_message.size);
2020-12-13 22:19:40 +01:00
2021-05-16 09:18:27 +02:00
if ( strcmp ( codec_header_message . codec , " flac " ) = = 0 ) {
2020-12-20 00:41:32 +01:00
// TODO: maybe restart the whole thing if a new codec header is received while stream session is ongoing
2020-12-13 22:19:40 +01:00
2021-05-16 09:18:27 +02:00
ESP_LOGI ( TAG , " Codec : %s " , codec_header_message . codec ) ;
2021-02-15 20:53:31 +01:00
raw_stream_write ( raw_stream_writer_to_decoder , codec_header_message . payload , size ) ;
2020-12-13 22:19:40 +01:00
}
2021-05-16 09:18:27 +02:00
else if ( strcmp ( codec_header_message . codec , " opus " ) = = 0 ) {
2020-12-13 22:19:40 +01:00
uint32_t rate ;
memcpy ( & rate , start + 4 , sizeof ( rate ) ) ;
uint16_t bits ;
memcpy ( & bits , start + 8 , sizeof ( bits ) ) ;
memcpy ( & channels , start + 10 , sizeof ( channels ) ) ;
2021-05-23 21:36:06 +02:00
ESP_LOGI ( TAG , " %s sampleformat: %d:%d:%d " , codec_header_message . codec , rate , bits , channels ) ;
2020-12-13 22:19:40 +01:00
2021-05-16 09:18:27 +02:00
int error = 0 ;
2021-05-23 21:36:06 +02:00
if ( opusDecoder ! = NULL ) {
opus_decoder_destroy ( opusDecoder ) ;
opusDecoder = NULL ;
}
2021-05-16 09:18:27 +02:00
opusDecoder = opus_decoder_create ( rate , channels , & error ) ;
if ( error ! = 0 ) {
ESP_LOGI ( TAG , " Failed to init %s decoder " , codec_header_message . codec ) ;
2020-12-13 22:19:40 +01:00
2021-05-16 09:18:27 +02:00
}
2021-05-23 21:36:06 +02:00
ESP_LOGI ( TAG , " Initialized %s decoder " , codec_header_message . codec ) ;
2021-05-16 09:18:27 +02:00
//ESP_LOGI(TAG, "Codec : %s not implemented yet", codec_header_message.codec);
// return;
2020-12-13 22:19:40 +01:00
//ESP_LOGI(TAG, "Codec setting %d:%d:%d", rate,bits,channels);
}
else {
ESP_LOGI ( TAG , " Codec : %s not supported " , codec_header_message . codec ) ;
ESP_LOGI ( TAG , " Change encoder codec to flac in /etc/snapserver.conf on server " ) ;
return ;
}
if ( codecString ! = NULL ) {
free ( codecString ) ;
codecString = NULL ;
}
2021-05-16 09:18:27 +02:00
2020-12-13 22:19:40 +01:00
codecString = ( char * ) calloc ( strlen ( codec_header_message . codec ) + 1 , sizeof ( char ) ) ;
if ( codecString = = NULL ) {
ESP_LOGW ( TAG , " couldn't get memory for codec String " ) ;
}
else {
strcpy ( codecString , codec_header_message . codec ) ;
}
2021-02-12 14:54:08 +01:00
tv1 . tv_sec = base_message . sent . sec ;
tv1 . tv_usec = base_message . sent . usec ;
settimeofday ( & tv1 , NULL ) ;
2021-04-22 18:15:21 +02:00
ESP_LOGI ( TAG , " syncing clock to server %ld.%06ld " , tv1 . tv_sec , tv1 . tv_usec ) ;
// gettimeofday(&tv1, NULL);
// ESP_LOGI(TAG, "get time of day %ld.%06ld", tv1.tv_sec, tv1.tv_usec);
2021-02-12 14:54:08 +01:00
2020-12-13 22:19:40 +01:00
codec_header_message_free ( & codec_header_message ) ;
2021-02-12 14:54:08 +01:00
2020-12-13 22:19:40 +01:00
received_header = true ;
break ;
case SNAPCAST_MESSAGE_WIRE_CHUNK :
{
if ( ! received_header ) {
continue ;
}
2020-12-20 00:41:32 +01:00
wire_chunk_message_t wire_chunk_message ;
2020-12-13 22:19:40 +01:00
2020-12-20 00:41:32 +01:00
result = wire_chunk_message_deserialize ( & wire_chunk_message , start , size ) ;
2020-12-13 22:19:40 +01:00
if ( result ) {
ESP_LOGI ( TAG , " Failed to read wire chunk: %d \r \n " , result ) ;
2020-12-20 00:41:32 +01:00
wire_chunk_message_free ( & wire_chunk_message ) ;
2020-12-13 22:19:40 +01:00
break ;
}
2021-04-22 18:15:21 +02:00
// ESP_LOGI(TAG, "wire chnk with size: %d, timestamp %d.%d", wire_chunk_message.size, wire_chunk_message.timestamp.sec, wire_chunk_message.timestamp.usec);
2020-12-13 22:19:40 +01:00
2021-04-23 08:46:23 +02:00
// TODO: detect pcm chunk duration dynamically and allocate buffers accordingly.
2021-05-16 09:18:27 +02:00
struct timeval tv_d1 , tv_d2 , tv_d3 ;
tv_d1 . tv_sec = wire_chunk_message . timestamp . sec ;
tv_d1 . tv_usec = wire_chunk_message . timestamp . usec ;
tv_d2 . tv_sec = wire_chunk_message_last . timestamp . sec ;
tv_d2 . tv_usec = wire_chunk_message_last . timestamp . usec ;
timersub ( & tv_d1 , & tv_d2 , & tv_d3 ) ;
2021-05-23 21:36:06 +02:00
wire_chunk_message_last . timestamp = wire_chunk_message . timestamp ;
2021-02-12 14:54:08 +01:00
// ESP_LOGI(TAG, "chunk duration %ld.%06ld", tv_d3.tv_sec, tv_d3.tv_usec);
2021-05-16 09:18:27 +02:00
if ( ( tv_d3 . tv_sec * 1000000 + tv_d3 . tv_usec ) > ( WIRE_CHUNK_DURATION_MS * 1000 ) ) {
ESP_LOGE ( TAG , " wire chnk with size: %d, timestamp %d.%d, duration %ld.%06ld " , wire_chunk_message . size , wire_chunk_message . timestamp . sec , wire_chunk_message . timestamp . usec , tv_d3 . tv_sec , tv_d3 . tv_usec ) ;
2021-02-12 14:54:08 +01:00
2021-05-23 21:36:06 +02:00
wire_chunk_message_free ( & wire_chunk_message ) ;
continue ;
}
2021-02-12 14:54:08 +01:00
2021-05-16 09:18:27 +02:00
//
// // store chunk's timestamp, decoder callback will need it later
2020-12-20 00:41:32 +01:00
tv_t timestamp ;
timestamp = wire_chunk_message . timestamp ;
2020-12-13 22:19:40 +01:00
2021-04-23 08:46:23 +02:00
// if (wirechnkCnt == 0) {
// ESP_LOGI(TAG, "set decoder timeout");
2021-04-22 18:15:21 +02:00
//
//// audio_element_set_input_timeout(decoder, pdMS_TO_TICKS(100));
//// audio_element_set_output_timeout(decoder, pdMS_TO_TICKS(100));
2021-04-23 08:46:23 +02:00
// }
2021-04-22 18:15:21 +02:00
// wirechnkCnt++;
2021-03-06 07:23:44 +01:00
// ESP_LOGI(TAG, "got wire chunk %d, cnt %lld",(int)wire_chunk_message.size, wirechnkCnt );
// ESP_LOGI(TAG, "got wire chunk cnt %lld", wirechnkCnt );
// ESP_LOGI(TAG, "wirechnkCnt: %lld", wirechnkCnt);
2020-12-22 20:47:33 +01:00
2021-05-16 11:19:58 +02:00
//if (strcmp(codecString, "flac") == 0) {
if ( 0 ) { // flac not supported
2021-05-16 09:18:27 +02:00
int bytesWritten ;
bytesWritten = raw_stream_write ( raw_stream_writer_to_decoder , wire_chunk_message . payload , ( int ) wire_chunk_message . size ) ;
if ( bytesWritten < 0 ) {
ESP_LOGE ( TAG , " wirechnk decode ring buf timeout. bytes in buffer: %d/%d " , rb_bytes_filled ( audio_element_get_output_ringbuf ( raw_stream_writer_to_decoder ) ) ,
audio_element_get_output_ringbuf_size ( raw_stream_writer_to_decoder ) ) ;
}
else if ( bytesWritten < ( int ) wire_chunk_message . size ) {
ESP_LOGE ( TAG , " wirechnk decode ring buf full " ) ;
}
else {
if ( xQueueSendToBack ( timestampQueueHandle , & timestamp , pdMS_TO_TICKS ( 3000 ) ) ! = pdTRUE ) {
ESP_LOGW ( TAG , " timestamp queue full, messages waiting %d, dropping data ... " , uxQueueMessagesWaiting ( timestampQueueHandle ) ) ;
}
else {
//ESP_LOGI(TAG, "timestamps waiting %d", uxQueueMessagesWaiting(timestampQueueHandle));
}
}
2021-03-06 07:23:44 +01:00
}
2021-05-16 09:18:27 +02:00
else if ( strcmp ( codecString , " opus " ) = = 0 ) {
int frame_size = 0 ;
2021-05-23 21:36:06 +02:00
2021-05-16 09:18:27 +02:00
audio = ( int16_t * ) heap_caps_malloc ( frameSize * CHANNELS * ( BITS_PER_SAMPLE / 8 ) , MALLOC_CAP_SPIRAM ) ; // 960*2: 20ms, 960*1: 10ms
if ( audio = = NULL ) {
ESP_LOGE ( TAG , " Failed to allocate memory for opus audio decoder " ) ;
2021-02-12 14:54:08 +01:00
}
else {
2021-05-16 09:18:27 +02:00
size = wire_chunk_message . size ;
start = ( wire_chunk_message . payload ) ;
while ( ( frame_size = opus_decode ( opusDecoder , ( unsigned char * ) start , size , ( opus_int16 * ) audio ,
pcm_size / channels , 0 ) ) = = OPUS_BUFFER_TOO_SMALL )
{
pcm_size = pcm_size * 2 ;
2021-05-16 11:19:58 +02:00
audio = ( int16_t * ) realloc ( audio , pcm_size * CHANNELS * sizeof ( int16_t ) ) ; // 960*2: 20ms, 960*1: 10ms
2021-05-16 09:18:27 +02:00
ESP_LOGI ( TAG , " OPUS encoding buffer too small, resizing to %d samples per channel " , pcm_size / channels ) ;
}
if ( frame_size < 0 ) {
ESP_LOGE ( TAG , " Decode error : %d, %d, %s, %s, %d \n " , frame_size , size , start , ( char * ) audio , pcm_size / channels ) ;
free ( audio ) ;
}
else {
wire_chunk_message_t * pcm_chunk_message ;
pcm_chunk_message = ( wire_chunk_message_t * ) heap_caps_malloc ( sizeof ( wire_chunk_message_t ) , MALLOC_CAP_SPIRAM ) ;
if ( pcm_chunk_message = = NULL ) {
ESP_LOGE ( TAG , " Failed to allocate memory for pcm chunk message " ) ;
}
else {
pcm_chunk_message - > size = frame_size * 2 * sizeof ( uint16_t ) ;
pcm_chunk_message - > timestamp = timestamp ;
pcm_chunk_message - > payload = ( char * ) audio ;
2021-05-16 11:19:58 +02:00
if ( xQueueSendToBack ( pcmChunkQueueHandle , & pcm_chunk_message , pdMS_TO_TICKS ( 1000 ) ) ! = pdTRUE ) {
ESP_LOGW ( TAG , " send: pcmChunkQueue full, messages waiting %d " , uxQueueMessagesWaiting ( pcmChunkQueueHandle ) ) ;
2021-05-23 21:36:06 +02:00
// free(pcm_chunk_message->payload);
// free(pcm_chunk_message);
2021-05-16 09:18:27 +02:00
}
}
}
2021-02-12 14:54:08 +01:00
}
2020-12-13 22:19:40 +01:00
}
2021-05-16 09:18:27 +02:00
else {
ESP_LOGE ( TAG , " Decoder not supported " ) ;
}
2020-12-13 22:19:40 +01:00
2021-05-24 15:47:24 +02:00
2020-12-20 00:41:32 +01:00
wire_chunk_message_free ( & wire_chunk_message ) ;
2020-12-13 22:19:40 +01:00
break ;
}
case SNAPCAST_MESSAGE_SERVER_SETTINGS :
// The first 4 bytes in the buffer are the size of the string.
// We don't need this, so we'll shift the entire buffer over 4 bytes
// and use the extra room to add a null character so cJSON can pares it.
memmove ( start , start + 4 , size - 4 ) ;
start [ size - 3 ] = ' \0 ' ;
result = server_settings_message_deserialize ( & server_settings_message , start ) ;
if ( result ) {
ESP_LOGI ( TAG , " Failed to read server settings: %d \r \n " , result ) ;
return ;
}
// log mute state, buffer, latency
buffer_ms = server_settings_message . buffer_ms ;
ESP_LOGI ( TAG , " Buffer length: %d " , server_settings_message . buffer_ms ) ;
//ESP_LOGI(TAG, "Ringbuffer size:%d", server_settings_message.buffer_ms*48*4);
ESP_LOGI ( TAG , " Latency: %d " , server_settings_message . latency ) ;
ESP_LOGI ( TAG , " Mute: %d " , server_settings_message . muted ) ;
ESP_LOGI ( TAG , " Setting volume: %d " , server_settings_message . volume ) ;
muteCH [ 0 ] = server_settings_message . muted ;
muteCH [ 1 ] = server_settings_message . muted ;
muteCH [ 2 ] = server_settings_message . muted ;
muteCH [ 3 ] = server_settings_message . muted ;
// Volume setting using ADF HAL abstraction
audio_hal_set_mute ( board_handle - > audio_hal , server_settings_message . muted ) ;
audio_hal_set_volume ( board_handle - > audio_hal , server_settings_message . volume ) ;
2021-04-23 08:46:23 +02:00
if ( syncTaskHandle = = NULL ) {
ESP_LOGI ( TAG , " Start snapcast_sync_task " ) ;
2020-12-13 22:19:40 +01:00
2021-05-23 21:36:06 +02:00
// snapcastTaskCfg.outputBufferDacTime_us = outputBufferDacTime_us;
// snapcastTaskCfg.buffer_us = (int64_t)buffer_ms * 1000LL;
xTaskCreatePinnedToCore ( snapcast_sync_task , " snapcast_sync_task " , 8 * 1024 , NULL , SYNC_TASK_PRIORITY , & syncTaskHandle , SYNC_TASK_CORE_ID ) ;
2021-04-23 08:46:23 +02:00
}
2020-12-13 22:19:40 +01:00
2021-05-23 21:36:06 +02:00
// notify task of changed parameters
xTaskNotify ( syncTaskHandle ,
buffer_ms ,
eSetBits
) ;
2020-12-13 22:19:40 +01:00
break ;
case SNAPCAST_MESSAGE_TIME :
result = time_message_deserialize ( & time_message , start , size ) ;
if ( result ) {
ESP_LOGI ( TAG , " Failed to deserialize time message \r \n " ) ;
return ;
}
// ESP_LOGI(TAG, "BaseTX : %d %d ", base_message.sent.sec , base_message.sent.usec);
// ESP_LOGI(TAG, "BaseRX : %d %d ", base_message.received.sec , base_message.received.usec);
// ESP_LOGI(TAG, "baseTX->RX : %d s ", (base_message.received.sec - base_message.sent.sec));
// ESP_LOGI(TAG, "baseTX->RX : %d ms ", (base_message.received.usec - base_message.sent.usec)/1000);
// ESP_LOGI(TAG, "Latency : %d.%d ", time_message.latency.sec, time_message.latency.usec/1000);
// tv == server to client latency (s2c)
// time_message.latency == client to server latency(c2s)
// TODO the fact that I have to do this simple conversion means
// I should probably use the timeval struct instead of my own
tv1 . tv_sec = base_message . received . sec ;
tv1 . tv_usec = base_message . received . usec ;
tv3 . tv_sec = base_message . sent . sec ;
tv3 . tv_usec = base_message . sent . usec ;
timersub ( & tv1 , & tv3 , & tv2 ) ;
tv1 . tv_sec = time_message . latency . sec ;
tv1 . tv_usec = time_message . latency . usec ;
// tv1 == c2s: client to server
// tv2 == s2c: server to client
// ESP_LOGI(TAG, "c2s: %ld %ld", tv1.tv_sec, tv1.tv_usec);
// ESP_LOGI(TAG, "s2c: %ld %ld", tv2.tv_sec, tv2.tv_usec);
timersub ( & tv1 , & tv2 , & tmpDiffToServer ) ;
if ( ( tmpDiffToServer . tv_sec / 2 ) = = 0 ) {
tmpDiffToServer . tv_sec = 0 ;
tmpDiffToServer . tv_usec = ( suseconds_t ) ( ( int64_t ) tmpDiffToServer . tv_sec * 1000000LL / 2 ) + tmpDiffToServer . tv_usec / 2 ;
}
else
{
tmpDiffToServer . tv_sec / = 2 ;
tmpDiffToServer . tv_usec / = 2 ;
}
2021-04-22 18:15:21 +02:00
// ESP_LOGI(TAG, "Current latency: %ld.%06ld", tmpDiffToServer.tv_sec, tmpDiffToServer.tv_usec);
2020-12-13 22:19:40 +01:00
2020-12-20 00:41:32 +01:00
// following code is storing / initializing / resetting diff to server algorithm
2021-04-23 08:46:23 +02:00
// we collect a number of latencies and apply a median filter. Based on these we can get server now
2020-12-20 00:41:32 +01:00
{
struct timeval diff ;
2021-04-23 08:46:23 +02:00
int64_t medianValue , newValue ;
2021-02-25 23:54:59 +01:00
2020-12-20 00:41:32 +01:00
// clear diffBuffer if last update is older than a minute
timersub ( & now , & lastTimeSync , & diff ) ;
2021-03-06 07:23:44 +01:00
2020-12-20 00:41:32 +01:00
if ( diff . tv_sec > 60 ) {
ESP_LOGW ( TAG , " Last time sync older than a minute. Clearing time buffer " ) ;
2021-04-23 08:46:23 +02:00
reset_latency_buffer ( ) ;
}
newValue = ( ( int64_t ) tmpDiffToServer . tv_sec * 1000000LL + ( int64_t ) tmpDiffToServer . tv_usec ) ;
2021-04-25 21:14:46 +02:00
medianValue = MEDIANFILTER_Insert ( & latencyMedianFilterLong , newValue ) ;
2021-05-03 06:39:30 +02:00
if ( xSemaphoreTake ( latencyBufSemaphoreHandle , pdMS_TO_TICKS ( 1 ) ) = = pdTRUE ) {
2021-05-16 09:18:27 +02:00
if ( MEDIANFILTER_isFull ( & latencyMedianFilterLong ) ) {
latencyBuffFull = true ;
}
2021-05-16 11:19:58 +02:00
latencyToServer = medianValue ;
2020-12-20 00:41:32 +01:00
2021-04-23 08:46:23 +02:00
xSemaphoreGive ( latencyBufSemaphoreHandle ) ;
}
else {
2021-05-03 06:39:30 +02:00
ESP_LOGW ( TAG , " couldn't set latencyToServer = medianValue " ) ;
2021-04-23 08:46:23 +02:00
}
2021-04-23 06:31:32 +02:00
2021-03-06 07:23:44 +01:00
// store current time
lastTimeSync . tv_sec = now . tv_sec ;
lastTimeSync . tv_usec = now . tv_usec ;
2021-02-25 23:54:59 +01:00
2021-03-06 07:23:44 +01:00
// we don't care if it was already taken, just make sure it is taken at this point
xSemaphoreTake ( timeSyncSemaphoreHandle , 0 ) ;
2021-02-25 23:54:59 +01:00
2021-04-25 21:14:46 +02:00
if ( latency_buffer_full ( ) > 0 ) {
2021-03-06 07:23:44 +01:00
// we give timeSyncSemaphoreHandle after x µs through timer
esp_timer_start_periodic ( timeSyncMessageTimer , 1000000 ) ;
}
else {
// Do a initial time sync with the server at boot
// give semaphore for immediate next run
// we need to fill diffBuff fast so we get a good estimate of latency
xSemaphoreGive ( timeSyncSemaphoreHandle ) ;
}
2020-12-20 00:41:32 +01:00
}
2020-12-13 22:19:40 +01:00
break ;
}
2020-04-23 00:15:11 +02:00
}
2020-12-13 22:19:40 +01:00
2021-02-25 23:54:59 +01:00
if ( received_header = = true ) {
2021-03-06 07:23:44 +01:00
if ( xSemaphoreTake ( timeSyncSemaphoreHandle , 0 ) = = pdTRUE ) {
2021-04-22 18:15:21 +02:00
result = gettimeofday ( & now , NULL ) ;
//ESP_LOGI(TAG, "time of day: %ld %ld", now.tv_sec, now.tv_usec);
if ( result ) {
ESP_LOGI ( TAG , " Failed to gettimeofday " ) ;
continue ;
}
2021-02-25 23:54:59 +01:00
base_message . type = SNAPCAST_MESSAGE_TIME ;
base_message . id = id_counter + + ;
base_message . refersTo = 0 ;
base_message . received . sec = 0 ;
base_message . received . usec = 0 ;
base_message . sent . sec = now . tv_sec ;
base_message . sent . usec = now . tv_usec ;
base_message . size = TIME_MESSAGE_SIZE ;
result = base_message_serialize (
& base_message ,
base_message_serialized ,
BASE_MESSAGE_SIZE
) ;
if ( result ) {
ESP_LOGE ( TAG , " Failed to serialize base message for time \r \n " ) ;
continue ;
}
2020-04-23 00:15:11 +02:00
2021-02-25 23:54:59 +01:00
result = time_message_serialize ( & time_message , buff , BUFF_LEN ) ;
if ( result ) {
ESP_LOGI ( TAG , " Failed to serialize time message \r \b " ) ;
continue ;
2021-05-24 15:47:24 +02:00
}
2020-04-23 00:15:11 +02:00
2021-05-24 15:47:24 +02:00
result = send ( sock , base_message_serialized , BASE_MESSAGE_SIZE , 0 ) ;
if ( result < 0 ) {
ESP_LOGW ( TAG , " error writing timesync base msg to socket: %s " , strerror ( errno ) ) ;
2020-12-13 22:19:40 +01:00
2021-05-24 15:47:24 +02:00
shutdown ( sock , 2 ) ;
closesocket ( sock ) ;
2020-12-13 22:19:40 +01:00
2021-05-24 15:47:24 +02:00
break ; // stop for(;;) will try to reconnect then
2021-05-16 11:19:58 +02:00
}
2021-05-23 21:36:06 +02:00
2021-05-24 15:47:24 +02:00
result = send ( sock , buff , TIME_MESSAGE_SIZE , 0 ) ;
if ( result < 0 ) {
ESP_LOGW ( TAG , " error writing timesync msg to socket: %s " , strerror ( errno ) ) ;
2021-05-23 21:36:06 +02:00
2021-05-24 15:47:24 +02:00
shutdown ( sock , 2 ) ;
closesocket ( sock ) ;
2021-05-23 21:36:06 +02:00
2021-05-24 15:47:24 +02:00
break ; // stop for(;;) will try to reconnect then
}
2021-05-23 21:36:06 +02:00
2021-05-24 15:47:24 +02:00
// ESP_LOGI(TAG, "sent time sync message %ld.%06ld", now.tv_sec, now.tv_usec);
}
}
2021-05-23 21:36:06 +02:00
}
2020-04-23 00:15:11 +02:00
}
}
2020-05-24 22:55:19 +02:00
2020-12-13 22:19:40 +01:00
/**
*
*/
2020-05-24 22:55:19 +02:00
void sntp_sync_time ( struct timeval * tv_ntp ) {
if ( ( sntp_synced % 10 ) = = 0 ) {
2020-12-13 22:19:40 +01:00
settimeofday ( tv_ntp , NULL ) ;
2020-05-24 22:55:19 +02:00
sntp_synced + + ;
ESP_LOGI ( TAG , " SNTP time set from server number :%d " , sntp_synced ) ;
2020-12-13 22:19:40 +01:00
return ;
2020-05-24 22:55:19 +02:00
}
2020-12-13 22:19:40 +01:00
sntp_synced + + ;
2020-05-24 22:55:19 +02:00
struct timeval tv_esp ;
gettimeofday ( & tv_esp , NULL ) ;
//ESP_LOGI(TAG,"SNTP diff s: %ld , %ld ", tv_esp.tv_sec , tv_ntp->tv_sec);
ESP_LOGI ( TAG , " SNTP diff us: %ld , %ld " , tv_esp . tv_usec , tv_ntp - > tv_usec ) ;
ESP_LOGI ( TAG , " SNTP diff us: %.2f " , ( double ) ( ( tv_esp . tv_usec - tv_ntp - > tv_usec ) / 1000.0 ) ) ;
2020-12-13 22:19:40 +01:00
2020-05-24 22:55:19 +02:00
}
2020-12-13 22:19:40 +01:00
/**
*
*/
2020-12-20 00:41:32 +01:00
void sntp_cb ( struct timeval * tv ) {
struct tm timeinfo = { 0 } ;
2020-12-13 22:19:40 +01:00
time_t now = tv - > tv_sec ;
2020-05-24 22:55:19 +02:00
localtime_r ( & now , & timeinfo ) ;
char strftime_buf [ 64 ] ;
strftime ( strftime_buf , sizeof ( strftime_buf ) , " %c " , & timeinfo ) ;
2020-12-13 22:19:40 +01:00
ESP_LOGI ( TAG , " sntp_cb called :%s " , strftime_buf ) ;
2020-05-24 22:55:19 +02:00
}
2020-04-23 00:15:11 +02:00
2020-12-13 22:19:40 +01:00
/**
*
*/
2020-04-23 00:15:11 +02:00
void set_time_from_sntp ( ) {
2021-04-23 22:33:05 +02:00
xEventGroupWaitBits ( s_wifi_event_group , WIFI_CONNECTED_EVENT ,
2020-04-23 00:15:11 +02:00
false , true , portMAX_DELAY ) ;
2020-04-27 08:17:14 +02:00
//ESP_LOGI(TAG, "clock %");
2020-12-13 22:19:40 +01:00
2020-04-23 00:15:11 +02:00
ESP_LOGI ( TAG , " Initializing SNTP " ) ;
sntp_setoperatingmode ( SNTP_OPMODE_POLL ) ;
2020-04-27 08:17:14 +02:00
sntp_setservername ( 0 , " europe.pool.ntp.org " ) ;
2020-04-25 00:15:49 +02:00
sntp_init ( ) ;
2020-05-24 22:55:19 +02:00
sntp_set_time_sync_notification_cb ( sntp_cb ) ;
setenv ( " TZ " , " UTC-2 " , 1 ) ;
tzset ( ) ;
2020-04-23 00:15:11 +02:00
2020-12-13 22:19:40 +01:00
/*
time_t now = 0 ;
2020-04-23 00:15:11 +02:00
struct tm timeinfo = { 0 } ;
int retry = 0 ;
const int retry_count = 10 ;
while ( timeinfo . tm_year < ( 2016 - 1900 ) & & + + retry < retry_count ) {
ESP_LOGI ( TAG , " Waiting for system time to be set... (%d/%d) " , retry , retry_count ) ;
vTaskDelay ( 2000 / portTICK_PERIOD_MS ) ;
time ( & now ) ;
localtime_r ( & now , & timeinfo ) ;
}
char strftime_buf [ 64 ] ;
2020-12-13 22:19:40 +01:00
2020-04-23 00:15:11 +02:00
strftime ( strftime_buf , sizeof ( strftime_buf ) , " %c " , & timeinfo ) ;
ESP_LOGI ( TAG , " The current date/time in UTC is: %s " , strftime_buf ) ;
2020-12-13 22:19:40 +01:00
*/
2020-04-23 00:15:11 +02:00
}
2021-03-06 07:23:44 +01:00
2021-04-22 18:15:21 +02:00
# define CONFIG_MASTER_I2S_BCK_PIN 5
# define CONFIG_MASTER_I2S_LRCK_PIN 25
# define CONFIG_MASTER_I2S_DATAOUT_PIN 26
# define CONFIG_SLAVE_I2S_BCK_PIN 26
# define CONFIG_SLAVE_I2S_LRCK_PIN 12
# define CONFIG_SLAVE_I2S_DATAOUT_PIN 5
2021-03-06 07:23:44 +01:00
2021-05-11 05:44:56 +02:00
esp_err_t setup_dsp_i2s ( uint32_t sample_rate , i2s_port_t i2sNum )
2021-04-22 18:15:21 +02:00
{
2021-05-11 05:44:56 +02:00
int chunkInFrames = chunkInBytes / ( CHANNELS * ( BITS_PER_SAMPLE / 8 ) ) ;
2021-05-16 09:18:27 +02:00
int __dmaBufCnt ;
int __dmaBufLen ;
const int __dmaBufMaxLen = 1024 ;
__dmaBufCnt = 1 ;
__dmaBufLen = chunkInFrames ;
while ( ( __dmaBufLen > = __dmaBufMaxLen ) | | ( __dmaBufCnt < = 1 ) ) {
if ( ( __dmaBufLen % 2 ) = = 0 ) {
2021-05-16 11:19:58 +02:00
__dmaBufCnt * = 2 ;
2021-05-16 09:18:27 +02:00
__dmaBufLen / = 2 ;
}
else {
ESP_LOGE ( TAG , " setup_dsp_i2s: Can't setup i2s with this configuration " ) ;
2021-05-11 05:44:56 +02:00
2021-05-16 09:18:27 +02:00
return - 1 ;
}
2021-05-11 05:44:56 +02:00
}
2021-05-16 09:18:27 +02:00
i2sDmaLAtency = ( 1000000LL * __dmaBufLen / SAMPLE_RATE ) * 0.8 ; // this value depends on __dmaBufLen, optimized for __dmaBufLen = 480 @opus, 192000bps, complexity 5
// it will be smaller for lower values of __dmaBufLen. The delay was measured against raspberry client
// TODO: find a way to calculate this value without the need to measure delay to another client
2021-05-16 11:19:58 +02:00
i2sDmaBufCnt = __dmaBufCnt * 2 + 1 ; // we do double buffering of chunks at I2S DMA, +1 needed because of the way i2s driver works, it will only allocate (i2sDmaBufCnt - 1) queue elements
2021-05-16 09:18:27 +02:00
ESP_LOGI ( TAG , " setup_dsp_i2s: dma_buf_len is %d, dma_buf_count is %d " , __dmaBufLen , __dmaBufCnt * 2 ) ;
2021-05-11 05:44:56 +02:00
i2s_config_t i2s_config0 = {
. mode = I2S_MODE_MASTER | I2S_MODE_TX , // Only TX
. sample_rate = sample_rate ,
. bits_per_sample = BITS_PER_SAMPLE ,
. channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT , // 2-channels
. communication_format = I2S_COMM_FORMAT_STAND_I2S ,
2021-05-16 09:18:27 +02:00
. dma_buf_count = i2sDmaBufCnt ,
. dma_buf_len = __dmaBufLen ,
2021-05-11 05:44:56 +02:00
. intr_alloc_flags = 1 , //Default interrupt priority
. use_apll = true ,
. fixed_mclk = 0 ,
. tx_desc_auto_clear = true // Auto clear tx descriptor on underflow
} ;
i2s_pin_config_t pin_config0 = {
. bck_io_num = CONFIG_MASTER_I2S_BCK_PIN ,
. ws_io_num = CONFIG_MASTER_I2S_LRCK_PIN ,
. data_out_num = CONFIG_MASTER_I2S_DATAOUT_PIN ,
. data_in_num = - 1 //Not used
} ;
2021-05-16 09:18:27 +02:00
i2s_driver_install ( i2sNum , & i2s_config0 , 1 , & i2s_event_queue ) ;
2021-05-11 05:44:56 +02:00
i2s_set_pin ( i2sNum , & pin_config0 ) ;
2021-05-16 09:18:27 +02:00
return 0 ;
2021-05-11 05:44:56 +02:00
}
2020-12-13 22:19:40 +01:00
/**
*
*/
2020-12-20 00:41:32 +01:00
void app_main ( void ) {
2020-12-13 22:19:40 +01:00
esp_err_t ret ;
uint8_t base_mac [ 6 ] ;
2021-05-03 06:39:30 +02:00
//ESP_ERROR_CHECK(nvs_flash_erase());
2021-04-23 22:33:05 +02:00
ret = nvs_flash_init ( ) ;
if ( ret = = ESP_ERR_NVS_NO_FREE_PAGES | | ret = = ESP_ERR_NVS_NEW_VERSION_FOUND ) {
2021-05-16 11:19:58 +02:00
// NVS partition was truncated
// and needs to be erased
2021-04-23 22:33:05 +02:00
ESP_ERROR_CHECK ( nvs_flash_erase ( ) ) ;
2021-03-06 07:23:44 +01:00
2021-04-23 22:33:05 +02:00
/* Retry nvs_flash_init */
ESP_ERROR_CHECK ( nvs_flash_init ( ) ) ;
}
{ // init wifi
// Initialize TCP/IP
ESP_ERROR_CHECK ( esp_netif_init ( ) ) ;
// Initialize the event loop
ESP_ERROR_CHECK ( esp_event_loop_create_default ( ) ) ;
s_wifi_event_group = xEventGroupCreate ( ) ;
wifi_init ( ) ;
}
2020-04-24 14:47:43 +02:00
2021-03-06 07:23:44 +01:00
esp_timer_init ( ) ;
2021-05-23 21:36:06 +02:00
tg0_timer_init ( ) ; // initialize initial sync timer
2020-12-13 22:19:40 +01:00
// Get MAC address for WiFi station
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 , " MAC Adress is: %s " , mac_address ) ;
2020-04-23 00:15:11 +02:00
2020-12-13 22:19:40 +01:00
esp_log_level_set ( " * " , ESP_LOG_WARN ) ;
esp_log_level_set ( TAG , ESP_LOG_INFO ) ;
2020-04-23 00:15:11 +02:00
2020-12-20 00:41:32 +01:00
ESP_LOGI ( TAG , " Start codec chip " ) ;
2020-12-13 22:19:40 +01:00
board_handle = audio_board_init ( ) ;
audio_hal_ctrl_codec ( board_handle - > audio_hal , AUDIO_HAL_CODEC_MODE_DECODE , AUDIO_HAL_CTRL_START ) ;
2021-04-23 08:46:23 +02:00
i2s_mclk_gpio_select ( I2S_NUM_0 , GPIO_NUM_0 ) ;
2021-05-11 05:44:56 +02:00
ret = setup_dsp_i2s ( 48000 , I2S_NUM_0 ) ;
if ( ret < 0 ) {
return ;
}
2020-12-13 22:19:40 +01:00
2020-12-20 00:41:32 +01:00
ESP_LOGI ( TAG , " Listen for all pipeline events " ) ;
2020-12-13 22:19:40 +01:00
# if CONFIG_USE_SNTP == 1
2020-12-20 00:41:32 +01:00
// syncing to sntp
2020-12-13 22:19:40 +01:00
vTaskDelay ( 5000 / portTICK_PERIOD_MS ) ;
2020-12-20 00:41:32 +01:00
ESP_LOGI ( TAG , " Syncing to sntp " ) ;
2020-12-13 22:19:40 +01:00
set_time_from_sntp ( ) ;
# else
{
2021-04-23 08:46:23 +02:00
// don't use sntp, if server and client are too different, we get overflowing timevals,
// insted we sync our clock to the server on reception of codec header
2020-12-13 22:19:40 +01:00
struct timeval tv = {
. tv_sec = 0 ,
. tv_usec = 0 ,
} ;
char tmbuf [ 64 ] , buf [ 128 ] ;
struct tm * nowtm ;
time_t nowtime ;
settimeofday ( & tv , NULL ) ;
nowtime = tv . tv_sec ;
nowtm = localtime ( & nowtime ) ;
strftime ( tmbuf , sizeof ( tmbuf ) , " %Y-%m-%d %H:%M:%S " , nowtm ) ;
sprintf ( buf , " %s.%06ld " , tmbuf , tv . tv_usec ) ;
2020-12-20 00:41:32 +01:00
ESP_LOGI ( TAG , " Current time is %s " , buf ) ;
2020-12-13 22:19:40 +01:00
}
# endif
2020-12-20 00:41:32 +01:00
ESP_LOGI ( TAG , " Start snapclient task " ) ;
timestampQueueHandle = xQueueCreateStatic ( TIMESTAMP_QUEUE_LENGTH ,
sizeof ( tv_t ) ,
timestampQueueStorageArea ,
& timestampQueue
) ;
2021-05-16 09:18:27 +02:00
xTaskCreatePinnedToCore ( http_get_task , " http_get_task " , 4 * 4096 , NULL , HTTP_TASK_PRIORITY , NULL , HTTP_TASK_CORE_ID ) ;
2021-02-12 14:54:08 +01:00
# if COLLECT_RUNTIME_STATS == 1
xTaskCreatePinnedToCore ( stats_task , " stats " , 4096 , NULL , STATS_TASK_PRIO , NULL , tskNO_AFFINITY ) ;
# endif
2020-04-24 14:47:43 +02:00
2020-12-13 22:19:40 +01:00
while ( 1 ) {
audio_event_iface_msg_t msg ;
2020-12-20 00:41:32 +01:00
esp_err_t ret ;
2021-05-16 09:18:27 +02:00
vTaskDelay ( portMAX_DELAY ) ;
/*
2020-12-20 00:41:32 +01:00
// listen to events
ret = audio_event_iface_listen ( evt , & msg , portMAX_DELAY ) ;
2020-04-23 00:15:11 +02:00
if ( ret ! = ESP_OK ) {
ESP_LOGE ( TAG , " [ * ] Event interface error : %d " , ret ) ;
continue ;
}
2021-03-06 07:23:44 +01:00
if ( msg . source_type = = AUDIO_ELEMENT_TYPE_ELEMENT & & msg . source = = ( void * ) decoder & &
msg . cmd = = AEL_MSG_CMD_REPORT_MUSIC_INFO )
{
2020-12-13 22:19:40 +01:00
audio_element_info_t music_info = { 0 } ;
audio_element_getinfo ( decoder , & music_info ) ;
if ( codecString ! = NULL ) {
2021-02-12 14:54:08 +01:00
ESP_LOGI ( TAG , " [ * ] Receive music info from %s decoder, sample_rates=%d, bits=%d, ch=%d " ,
codecString , music_info . sample_rates , music_info . bits , music_info . channels ) ;
2020-12-13 22:19:40 +01:00
}
else {
ESP_LOGI ( TAG , " [ * ] Receive music info from decoder, sample_rates=%d, bits=%d, ch=%d " ,
music_info . sample_rates , music_info . bits , music_info . channels ) ;
}
continue ;
}
2021-03-06 07:23:44 +01:00
else if ( msg . source_type = = AUDIO_ELEMENT_TYPE_ELEMENT & & msg . source = = ( void * ) decoder & &
msg . cmd = = AEL_MSG_CMD_REPORT_STATUS )
{
ESP_LOGW ( TAG , " report status: %d " , ( int ) msg . data ) ;
}
else if ( msg . source_type = = AUDIO_ELEMENT_TYPE_ELEMENT & & msg . source = = ( void * ) decoder ) {
ESP_LOGW ( TAG , " decoder status: %d " , msg . cmd ) ;
}
2021-05-16 11:19:58 +02:00
*/
2020-12-13 22:19:40 +01:00
}
2020-12-20 00:41:32 +01:00
// TODO: clean up all created tasks and delete them
2020-04-23 00:15:11 +02:00
}
2020-12-13 22:19:40 +01:00