- add sdkconfig for NO_SPIRAM and SPIRAM situation
- detect snapcast configuration and init everything accordingly, e.g sample rate, chunk duration, ... o calculate apll predefines in dependence of sample rate o communicate these settings to interested parties - remove typos
This commit is contained in:
@@ -230,7 +230,7 @@ static float i2s_apll_get_fi2s(int bits_per_sample, int sdm0, int sdm1, int sdm2
|
||||
* @return ESP_ERR_INVALID_ARG or ESP_OK
|
||||
*/
|
||||
|
||||
static esp_err_t i2s_apll_calculate_fi2s(int rate, int bits_per_sample, int *sdm0, int *sdm1, int *sdm2, int *odir)
|
||||
esp_err_t i2s_apll_calculate_fi2s(int rate, int bits_per_sample, int *sdm0, int *sdm1, int *sdm2, int *odir)
|
||||
{
|
||||
int _odir, _sdm0, _sdm1, _sdm2;
|
||||
float avg;
|
||||
|
||||
@@ -202,6 +202,42 @@ esp_err_t i2s_custom_write_expand(i2s_port_t i2s_num, const void *src, size_t si
|
||||
*/
|
||||
esp_err_t i2s_custom_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait);
|
||||
|
||||
/**
|
||||
* @brief APLL calculate function, was described by following:
|
||||
* APLL Output frequency is given by the formula:
|
||||
*
|
||||
* apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536)/((o_div + 2) * 2)
|
||||
* apll_freq = fout / ((o_div + 2) * 2)
|
||||
*
|
||||
* The dividend in this expression should be in the range of 240 - 600 MHz.
|
||||
* In rev. 0 of ESP32, sdm0 and sdm1 are unused and always set to 0.
|
||||
* * sdm0 frequency adjustment parameter, 0..255
|
||||
* * sdm1 frequency adjustment parameter, 0..255
|
||||
* * sdm2 frequency adjustment parameter, 0..63
|
||||
* * o_div frequency divider, 0..31
|
||||
*
|
||||
* The most accurate way to find the sdm0..2 and odir parameters is to loop through them all,
|
||||
* then apply the above formula, finding the closest frequency to the desired one.
|
||||
* But 256*256*64*32 = 134.217.728 loops are too slow with ESP32
|
||||
* 1. We will choose the parameters with the highest level of change,
|
||||
* With 350MHz<fout<500MHz, we limit the sdm2 from 4 to 9,
|
||||
* Take average frequency close to the desired frequency, and select sdm2
|
||||
* 2. Next, we look for sequences of less influential and more detailed parameters,
|
||||
* also by taking the average of the largest and smallest frequencies closer to the desired frequency.
|
||||
* 3. And finally, loop through all the most detailed of the parameters, finding the best desired frequency
|
||||
*
|
||||
* @param[in] rate The I2S Frequency (MCLK)
|
||||
* @param[in] bits_per_sample The bits per sample
|
||||
* @param[out] sdm0 The sdm 0
|
||||
* @param[out] sdm1 The sdm 1
|
||||
* @param[out] sdm2 The sdm 2
|
||||
* @param[out] odir The odir
|
||||
*
|
||||
* @return ESP_ERR_INVALID_ARG or ESP_OK
|
||||
*/
|
||||
|
||||
esp_err_t i2s_apll_calculate_fi2s(int rate, int bits_per_sample, int *sdm0, int *sdm1, int *sdm2, int *odir);
|
||||
|
||||
/**
|
||||
* @brief Set sample rate used for I2S RX and TX.
|
||||
*
|
||||
|
||||
@@ -1,42 +1,18 @@
|
||||
# Config file for ESP32 DSP Processor
|
||||
|
||||
menu "ESP32 audio buffer and I2S pin config"
|
||||
menu "ESP32 audio buffer and I2S config"
|
||||
config USE_PSRAM
|
||||
bool "Use PSRAM"
|
||||
default true
|
||||
depends on ESP32_SPIRAM_SUPPORT
|
||||
help
|
||||
Need wrover class modules with large SPRAM to have required buffers for Snapcast network delay
|
||||
Need wrover class modules with large SPRAM to have buffers >1s and chunks >20ms for Snapcast network delay
|
||||
|
||||
config USE_DSP_PROCESSOR
|
||||
bool "enable signal processing on audio data"
|
||||
default false
|
||||
help
|
||||
enable audio filtering before queueing it to player component
|
||||
|
||||
config BITS_PER_SAMPLE
|
||||
int "bits per sample output to i2s driver"
|
||||
default 16
|
||||
help
|
||||
Select number of bits per sample for codec configured and connected to esp32 i2s dma hw
|
||||
|
||||
config CHANNELS
|
||||
int "number of channels per sample output to i2s driver"
|
||||
default 2
|
||||
help
|
||||
Select number of channels per sample for codec configured and connected to esp32 i2s dma hw
|
||||
|
||||
config PCM_SAMPLE_RATE
|
||||
int "sample rate of audio pcm data"
|
||||
default 48000
|
||||
help
|
||||
sample rate of audio data, currently only 48kHz is heavily tested and used during development
|
||||
|
||||
config WIRE_CHUNK_DURATION_MS
|
||||
int "wire chunk duration [ms]"
|
||||
default 20
|
||||
help
|
||||
pcm data is encoded in chunks of x ms, this value has to match snapserver configuration
|
||||
|
||||
config USE_BIQUAD_ASM
|
||||
bool "Use uptimized asm version of Biquad_f32"
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
static const char *TAG = "dspProc";
|
||||
|
||||
static const uint8_t chunkDurationMs = CONFIG_WIRE_CHUNK_DURATION_MS;
|
||||
static const uint32_t sampleRate = CONFIG_PCM_SAMPLE_RATE;
|
||||
//static const uint8_t chunkDurationMs = CONFIG_WIRE_CHUNK_DURATION_MS;
|
||||
//static const uint32_t sampleRate = CONFIG_PCM_SAMPLE_RATE;
|
||||
//static const uint8_t channels = CONFIG_CHANNELS;
|
||||
//static const uint8_t bitsPerSample = CONFIG_BITS_PER_SAMPLE;
|
||||
|
||||
@@ -48,6 +48,9 @@ static float *sbuftmp0 = NULL;//[1024];
|
||||
|
||||
extern uint8_t muteCH[4];
|
||||
|
||||
static uint32_t currentSamplerate = 0;
|
||||
static uint32_t currentChunkDurationMs = 0;
|
||||
|
||||
ptype_t bq[8];
|
||||
|
||||
|
||||
@@ -265,10 +268,18 @@ int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow) {
|
||||
// ----------------------------------------
|
||||
// Fixed 2x2 biquad flow Xover for biAmp systems
|
||||
// Interface for cross over frequency and level
|
||||
|
||||
void dsp_setup_flow(double freq, uint32_t samplerate) {
|
||||
void dsp_setup_flow(double freq, uint32_t samplerate, uint32_t chunkDurationMs) {
|
||||
float f = freq / samplerate / 2.0;
|
||||
uint16_t len = (sampleRate * chunkDurationMs / 1000);
|
||||
uint16_t len = (samplerate * chunkDurationMs / 1000);
|
||||
|
||||
if (((currentSamplerate == samplerate) && (currentChunkDurationMs == chunkDurationMs)) ||
|
||||
(samplerate == 0) || (chunkDurationMs == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentSamplerate = samplerate;
|
||||
currentChunkDurationMs = chunkDurationMs;
|
||||
|
||||
bq[0] = (ptype_t){LPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}};
|
||||
bq[1] = (ptype_t){LPF, f, 0, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}};
|
||||
@@ -279,11 +290,11 @@ void dsp_setup_flow(double freq, uint32_t samplerate) {
|
||||
bq[6] = (ptype_t){LOWSHELF, f, 6, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}};
|
||||
bq[7] = (ptype_t){LOWSHELF, f, 6, 0.707, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0}};
|
||||
|
||||
pnode_t *aflow = NULL;
|
||||
aflow = malloc(sizeof(pnode_t));
|
||||
if (aflow == NULL) {
|
||||
printf("Could not create node");
|
||||
}
|
||||
// pnode_t *aflow = NULL;
|
||||
// aflow = malloc(sizeof(pnode_t));
|
||||
// if (aflow == NULL) {
|
||||
// printf("Could not create node");
|
||||
// }
|
||||
|
||||
for (uint8_t n = 0; n <= 7; n++) {
|
||||
switch (bq[n].filtertype) {
|
||||
@@ -306,6 +317,18 @@ void dsp_setup_flow(double freq, uint32_t samplerate) {
|
||||
// printf("\n");
|
||||
}
|
||||
|
||||
if (sbuffer0 != NULL) {
|
||||
free(sbuffer0);
|
||||
sbuffer0 = NULL;
|
||||
}
|
||||
if (sbufout0 != NULL) {
|
||||
free(sbufout0);
|
||||
sbufout0 = NULL;
|
||||
}
|
||||
if (sbuftmp0 != NULL) {
|
||||
free(sbuftmp0);
|
||||
sbuftmp0 = NULL;
|
||||
}
|
||||
|
||||
sbuffer0 = (float *)heap_caps_malloc(sizeof(float) * len, MALLOC_CAP_8BIT);
|
||||
sbufout0 = (float *)heap_caps_malloc(sizeof(float) * len, MALLOC_CAP_8BIT);
|
||||
|
||||
@@ -39,7 +39,7 @@ typedef struct pnode {
|
||||
struct pnode *next;
|
||||
} pnode_t;
|
||||
|
||||
void dsp_setup_flow(double freq, uint32_t samplerate);
|
||||
void dsp_setup_flow(double freq, uint32_t samplerate, uint32_t chunkDurationMs);
|
||||
int dsp_processor(char *audio, size_t chunk_size, dspFlows_t dspFlow);
|
||||
void dsp_set_xoverfreq(uint8_t, uint8_t, uint32_t);
|
||||
|
||||
|
||||
@@ -5,14 +5,7 @@
|
||||
#include "esp_types.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "snapcast.h"
|
||||
|
||||
// TODO: make the following configurable through menuconfig
|
||||
// @ 48kHz, 2ch, 16bit audio data and 24ms wirechunks (hardcoded for now) we
|
||||
// expect 0.024 * 2 * 16/8 * 48000 = 4608 Bytes
|
||||
#define WIRE_CHUNK_DURATION_MS CONFIG_WIRE_CHUNK_DURATION_MS
|
||||
#define SAMPLE_RATE CONFIG_PCM_SAMPLE_RATE
|
||||
#define CHANNELS CONFIG_CHANNELS
|
||||
#define BITS_PER_SAMPLE CONFIG_BITS_PER_SAMPLE
|
||||
#include "i2s.h"
|
||||
|
||||
#define I2S_PORT I2S_NUM_0
|
||||
|
||||
@@ -34,6 +27,21 @@ typedef struct pcm_chunk_message {
|
||||
pcm_chunk_fragment_t *fragment;
|
||||
} pcm_chunk_message_t;
|
||||
|
||||
typedef enum codec_type_e { NONE, PCM, FLAC, OGG, OPUS } codec_type_t;
|
||||
|
||||
typedef struct snapcastSetting_s {
|
||||
uint32_t buffer_ms;
|
||||
uint32_t chunkDuration_ms;
|
||||
|
||||
codec_type_t codec;
|
||||
int32_t sampleRate;
|
||||
uint8_t channels;
|
||||
i2s_bits_per_sample_t bits;
|
||||
|
||||
bool muted;
|
||||
uint32_t volume;
|
||||
} snapcastSetting_t;
|
||||
|
||||
QueueHandle_t init_player(void);
|
||||
int deinit_player(void);
|
||||
|
||||
@@ -42,13 +50,11 @@ int8_t free_pcm_chunk(pcm_chunk_message_t *pcmChunk);
|
||||
|
||||
int8_t player_latency_insert(int64_t newValue);
|
||||
int8_t player_notify_buffer_ms(uint32_t ms);
|
||||
int8_t player_send_snapcast_setting(snapcastSetting_t setting);
|
||||
|
||||
int8_t reset_latency_buffer(void);
|
||||
int8_t latency_buffer_full(void);
|
||||
int8_t get_diff_to_server(int64_t *tDiff);
|
||||
int8_t server_now(int64_t *sNow);
|
||||
|
||||
// void tg0_timer_init(void);
|
||||
// void snapcast_sync_task(void *pvParameters);
|
||||
|
||||
#endif // __PLAYER_H__
|
||||
|
||||
@@ -30,24 +30,14 @@
|
||||
static const char *TAG = "PLAYER";
|
||||
|
||||
/**
|
||||
* @brief Pre define APLL parameters, save compute time
|
||||
* @brief Pre define APLL parameters, save compute time. They are calculated in player_setup_i2s()
|
||||
* | bits_per_sample | rate | sdm0 | sdm1 | sdm2 | odir
|
||||
*
|
||||
* apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536)/((o_div +
|
||||
* 2) * 2) I2S bit clock is (apll_freq / 16)
|
||||
*/
|
||||
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}};
|
||||
|
||||
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
|
||||
{16, 48005, 213, 212, 5, 6}, // ~ 48kHz * 1.0001
|
||||
{16, 47995, 84, 212, 5, 6}, // ~ 48kHz * 0.9999
|
||||
};
|
||||
static int apll_normal_predefine[6] = {0, 0, 0, 0, 0, 0};
|
||||
static int apll_corr_predefine[][6] = {{0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}};
|
||||
|
||||
static SemaphoreHandle_t latencyBufSemaphoreHandle = NULL;
|
||||
|
||||
@@ -73,13 +63,15 @@ static uint8_t pcmChunkQueueStorageArea[PCM_CHNK_QUEUE_LENGTH *
|
||||
|
||||
static TaskHandle_t syncTaskHandle = NULL;
|
||||
|
||||
static const size_t chunkInBytes =
|
||||
(WIRE_CHUNK_DURATION_MS * SAMPLE_RATE * CHANNELS * (BITS_PER_SAMPLE / 8)) /
|
||||
1000;
|
||||
static QueueHandle_t snapcastSettingQueueHandle = NULL;
|
||||
|
||||
static size_t chunkInBytes;
|
||||
|
||||
static uint32_t i2sDmaBufCnt;
|
||||
static uint32_t i2sDmaBufMaxLen;
|
||||
|
||||
snapcastSetting_t currentSnapcastSetting;
|
||||
|
||||
static void tg0_timer_init(void);
|
||||
static void tg0_timer_deinit(void);
|
||||
static void snapcast_sync_task(void *pvParameters);
|
||||
@@ -93,11 +85,15 @@ static void snapcast_sync_task(void *pvParameters);
|
||||
#define CONFIG_SLAVE_I2S_DATAOUT_PIN 5
|
||||
*/
|
||||
|
||||
static esp_err_t player_setup_i2s(uint32_t sample_rate, i2s_port_t i2sNum) {
|
||||
int chunkInFrames = chunkInBytes / (CHANNELS * (BITS_PER_SAMPLE / 8));
|
||||
static esp_err_t player_setup_i2s(i2s_port_t i2sNum, snapcastSetting_t *setting) {
|
||||
int chunkInFrames;
|
||||
int __dmaBufCnt;
|
||||
int __dmaBufLen;
|
||||
const int __dmaBufMaxLen = 1024;
|
||||
int m_scale = 8, fi2s_clk;
|
||||
|
||||
chunkInBytes = (setting->chunkDuration_ms * setting->sampleRate * setting->channels * (setting->bits / 8)) / 1000;
|
||||
chunkInFrames = chunkInBytes / (setting->channels * (setting->bits / 8));
|
||||
|
||||
__dmaBufCnt = 1;
|
||||
__dmaBufLen = chunkInFrames;
|
||||
@@ -116,13 +112,32 @@ static esp_err_t player_setup_i2s(uint32_t sample_rate, i2s_port_t i2sNum) {
|
||||
i2sDmaBufCnt = __dmaBufCnt;
|
||||
i2sDmaBufMaxLen = __dmaBufLen;
|
||||
|
||||
fi2s_clk = setting->sampleRate * setting->channels * setting->bits * m_scale;
|
||||
|
||||
apll_normal_predefine[0] = setting->bits;
|
||||
apll_normal_predefine[1] = setting->sampleRate;
|
||||
if (i2s_apll_calculate_fi2s(fi2s_clk, setting->bits, &apll_normal_predefine[2], &apll_normal_predefine[3], &apll_normal_predefine[4], &apll_normal_predefine[5]) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ERROR, fi2s_clk");
|
||||
}
|
||||
|
||||
apll_corr_predefine[0][0] = setting->bits;
|
||||
apll_corr_predefine[0][1] = setting->sampleRate * 1.001;
|
||||
if (i2s_apll_calculate_fi2s(fi2s_clk * 1.001, setting->bits, &apll_corr_predefine[0][2], &apll_corr_predefine[0][3], &apll_corr_predefine[0][4], &apll_corr_predefine[0][5]) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ERROR, fi2s_clk * 1.001");
|
||||
}
|
||||
apll_corr_predefine[1][0] = setting->bits;
|
||||
apll_corr_predefine[1][1] = setting->sampleRate * 0.999;
|
||||
if (i2s_apll_calculate_fi2s(fi2s_clk * 0.999, setting->bits, &apll_corr_predefine[1][2], &apll_corr_predefine[1][3], &apll_corr_predefine[1][4], &apll_corr_predefine[1][5]) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ERROR, fi2s_clk * 0.999");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "player_setup_i2s: dma_buf_len is %d, dma_buf_count is %d",
|
||||
i2sDmaBufMaxLen, i2sDmaBufCnt);
|
||||
|
||||
i2s_config_t i2s_config0 = {
|
||||
.mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX
|
||||
.sample_rate = sample_rate,
|
||||
.bits_per_sample = BITS_PER_SAMPLE,
|
||||
.sample_rate = setting->sampleRate,
|
||||
.bits_per_sample = setting->bits,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 2-channels
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.dma_buf_count = i2sDmaBufCnt,
|
||||
@@ -136,6 +151,7 @@ static esp_err_t player_setup_i2s(uint32_t sample_rate, i2s_port_t i2sNum) {
|
||||
i2s_pin_config_t pin_config0;
|
||||
get_i2s_pins(i2sNum, &pin_config0);
|
||||
|
||||
i2s_custom_driver_uninstall(i2sNum);
|
||||
i2s_custom_driver_install(i2sNum, &i2s_config0, 0, NULL);
|
||||
i2s_custom_set_pin(i2sNum, &pin_config0);
|
||||
|
||||
@@ -193,7 +209,16 @@ int deinit_player(void) {
|
||||
QueueHandle_t init_player(void) {
|
||||
int ret;
|
||||
|
||||
ret = player_setup_i2s(SAMPLE_RATE, I2S_PORT);
|
||||
currentSnapcastSetting.buffer_ms = 1000;
|
||||
currentSnapcastSetting.chunkDuration_ms = 20;
|
||||
currentSnapcastSetting.codec = NONE;
|
||||
currentSnapcastSetting.sampleRate = 44100;
|
||||
currentSnapcastSetting.channels = 2;
|
||||
currentSnapcastSetting.bits = 16;
|
||||
currentSnapcastSetting.muted = false;
|
||||
currentSnapcastSetting.volume = 70;
|
||||
|
||||
ret = player_setup_i2s(I2S_NUM_0, ¤tSnapcastSetting);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "player_setup_i2s failed: %d", ret);
|
||||
|
||||
@@ -265,6 +290,36 @@ int8_t player_notify_buffer_ms(uint32_t ms) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int8_t player_send_snapcast_setting(snapcastSetting_t setting) {
|
||||
int ret;
|
||||
|
||||
if ((syncTaskHandle == NULL) || (snapcastSettingQueueHandle == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((currentSnapcastSetting.bits != setting.bits) ||
|
||||
(currentSnapcastSetting.buffer_ms != setting.buffer_ms) ||
|
||||
(currentSnapcastSetting.channels != setting.channels) ||
|
||||
(currentSnapcastSetting.chunkDuration_ms != setting.chunkDuration_ms) ||
|
||||
(currentSnapcastSetting.codec != setting.codec) ||
|
||||
(currentSnapcastSetting.muted != setting.muted) ||
|
||||
(currentSnapcastSetting.sampleRate != setting.sampleRate) ||
|
||||
(currentSnapcastSetting.volume != setting.volume))
|
||||
{
|
||||
// notify task of changed parameters
|
||||
memcpy(¤tSnapcastSetting, &setting, sizeof(snapcastSetting_t));
|
||||
ret = xQueueSend(snapcastSettingQueueHandle, ¤tSnapcastSetting, pdMS_TO_TICKS(10));
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "player_send_snapcast_setting: couldn't send snapcast setting");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -465,31 +520,53 @@ static void tg0_timer1_start(uint64_t alarm_value) {
|
||||
// rate * (number of channels) * bits_per_sample
|
||||
void adjust_apll(int8_t direction) {
|
||||
int sdm0, sdm1, sdm2, o_div;
|
||||
int index = 2; // 2 for slow adjustment, 0 for fast adjustment
|
||||
// int index = 2; // 2 for slow adjustment, 0 for fast adjustment
|
||||
|
||||
// only change if necessary
|
||||
if (currentDir == direction) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if (direction == 1) {
|
||||
// // speed up
|
||||
// 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];
|
||||
// } else if (direction == -1) {
|
||||
// // slow down
|
||||
// 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];
|
||||
// } 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;
|
||||
// }
|
||||
|
||||
if (direction == 1) {
|
||||
// speed up
|
||||
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];
|
||||
sdm0 = apll_corr_predefine[0][2];
|
||||
sdm1 = apll_corr_predefine[0][3];
|
||||
sdm2 = apll_corr_predefine[0][4];
|
||||
o_div = apll_corr_predefine[0][5];
|
||||
} else if (direction == -1) {
|
||||
// slow down
|
||||
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];
|
||||
sdm0 = apll_corr_predefine[1][2];
|
||||
sdm1 = apll_corr_predefine[1][3];
|
||||
sdm2 = apll_corr_predefine[1][4];
|
||||
o_div = apll_corr_predefine[1][5];
|
||||
} 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];
|
||||
sdm0 = apll_normal_predefine[2];
|
||||
sdm1 = apll_normal_predefine[3];
|
||||
sdm2 = apll_normal_predefine[4];
|
||||
o_div = apll_normal_predefine[5];
|
||||
|
||||
direction = 0;
|
||||
}
|
||||
@@ -593,6 +670,28 @@ int8_t insert_pcm_chunk(wire_chunk_message_t *decodedWireChunk) {
|
||||
// store the timestamp
|
||||
pcmChunk->timestamp = decodedWireChunk->timestamp;
|
||||
|
||||
#if CONFIG_USE_PSRAM
|
||||
pcmChunk->fragment->payload = (char *)heap_caps_malloc(decodedWireChunk->size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||
if (pcmChunk->fragment->payload == NULL) {
|
||||
ESP_LOGE(
|
||||
TAG,
|
||||
"Failed to allocate memory for pcm chunk fragment payload");
|
||||
|
||||
free_pcm_chunk(pcmChunk);
|
||||
|
||||
freeMem = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||
|
||||
ret = -2;
|
||||
}
|
||||
else {
|
||||
// copy the whole payload to our fragment
|
||||
memcpy(pcmChunk->fragment->payload, decodedWireChunk->payload, decodedWireChunk->size);
|
||||
pcmChunk->fragment->nextFragment = NULL;
|
||||
pcmChunk->fragment->size = decodedWireChunk->size;
|
||||
|
||||
ret = 0;
|
||||
}
|
||||
#else
|
||||
// we got valid memory for pcm_chunk_message_t
|
||||
// first we try to allocated 32 bit aligned memory for payload
|
||||
// check available memory first so we can decide if we need to fragment the data
|
||||
@@ -667,6 +766,7 @@ int8_t insert_pcm_chunk(wire_chunk_message_t *decodedWireChunk) {
|
||||
largestFreeBlock = heap_caps_get_largest_free_block(MALLOC_CAP_32BIT);
|
||||
if (largestFreeBlock <= tmpSize) {
|
||||
s = largestFreeBlock;
|
||||
// TODO: DMA buffer needs to be changed also to one chunk size if sample rate changes
|
||||
}
|
||||
else {
|
||||
s = tmpSize;
|
||||
@@ -799,6 +899,7 @@ int8_t insert_pcm_chunk(wire_chunk_message_t *decodedWireChunk) {
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ret == 0) {
|
||||
if (xQueueSendToBack(pcmChunkQueueHandle,
|
||||
@@ -828,10 +929,11 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
int64_t serverNow = 0;
|
||||
int64_t age;
|
||||
BaseType_t ret;
|
||||
int64_t chunkDuration_us = WIRE_CHUNK_DURATION_MS * 1000;
|
||||
int64_t chunkDuration_us;// = WIRE_CHUNK_DURATION_MS * 1000;
|
||||
char *p_payload = NULL;
|
||||
size_t size = 0;
|
||||
uint32_t notifiedValue;
|
||||
snapcastSetting_t snapcastSetting;
|
||||
uint64_t timer_val;
|
||||
const int32_t alarmValSub = 0;
|
||||
int initialSync = 0;
|
||||
@@ -840,13 +942,13 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
int64_t buffer_us_local = 0;
|
||||
pcm_chunk_fragment_t *fragment = NULL;
|
||||
size_t written;
|
||||
bool gotSnapserverConfig = false;
|
||||
|
||||
ESP_LOGI(TAG, "started sync task");
|
||||
|
||||
initialSync = 0;
|
||||
snapcastSettingQueueHandle = xQueueCreate(1, sizeof(snapcastSetting_t));
|
||||
|
||||
currentDir = 1; // force adjust_apll to set correct playback speed
|
||||
adjust_apll(0);
|
||||
initialSync = 0;
|
||||
|
||||
shortMedianFilter.numNodes = SHORT_BUFFER_LEN;
|
||||
shortMedianFilter.medianBuffer = shortMedianBuffer;
|
||||
@@ -857,14 +959,40 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
}
|
||||
|
||||
while (1) {
|
||||
// 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
|
||||
¬ifiedValue, // Stores the notified value.
|
||||
0);
|
||||
// check if we got changed setting available, if so we need to reinitialize
|
||||
ret = xQueueReceive(snapcastSettingQueueHandle, &snapcastSetting, 0);
|
||||
if (ret == pdTRUE) {
|
||||
buffer_us_local = (int64_t)(snapcastSetting.buffer_ms) * 1000LL;
|
||||
|
||||
buffer_us_local = (int64_t)notifiedValue * 1000LL;
|
||||
chunkDuration_us = (int64_t)(snapcastSetting.chunkDuration_ms) * 1000LL;
|
||||
chunkInBytes = (snapcastSetting.chunkDuration_ms * snapcastSetting.sampleRate * snapcastSetting.channels * (snapcastSetting.bits / 8)) / 1000;
|
||||
|
||||
if ((snapcastSetting.sampleRate > 0) && (snapcastSetting.bits) > 0 && (snapcastSetting.channels > 0)) {
|
||||
i2s_custom_stop(I2S_NUM_0);
|
||||
|
||||
ret = player_setup_i2s(I2S_NUM_0, ¤tSnapcastSetting);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "player_setup_i2s failed: %d", ret);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
currentDir = 1; // force adjust_apll to set correct playback speed
|
||||
adjust_apll(0);
|
||||
|
||||
i2s_custom_set_clk(I2S_NUM_0, snapcastSetting.sampleRate, snapcastSetting.bits, snapcastSetting.channels);
|
||||
initialSync = 0;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "snapserver config changed, buffer %dms, chunk %dms, sample rate %d, ch %d, bits %d", snapcastSetting.buffer_ms, snapcastSetting.chunkDuration_ms, snapcastSetting.sampleRate, snapcastSetting.channels, snapcastSetting.bits);
|
||||
|
||||
gotSnapserverConfig = true;
|
||||
}
|
||||
else if (gotSnapserverConfig == false) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chnk == NULL) {
|
||||
ret = xQueueReceive(pcmChunkQueueHandle, &chnk, pdMS_TO_TICKS(2000));
|
||||
@@ -931,7 +1059,7 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
// little earlier to account for context switch duration from
|
||||
// freeRTOS, timer with 100ns ticks
|
||||
|
||||
i2s_custom_stop(I2S_PORT);
|
||||
i2s_custom_stop(I2S_NUM_0);
|
||||
|
||||
if (MEDIANFILTER_Init(&shortMedianFilter)) {
|
||||
ESP_LOGE(
|
||||
@@ -947,7 +1075,7 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
p_payload = fragment->payload;
|
||||
size = fragment->size;
|
||||
|
||||
i2s_custom_init_dma_tx_queues(I2S_PORT, (uint8_t *)p_payload, size, &written);
|
||||
i2s_custom_init_dma_tx_queues(I2S_NUM_0, (uint8_t *)p_payload, size, &written);
|
||||
size -= written;
|
||||
p_payload += written;
|
||||
|
||||
@@ -973,7 +1101,7 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
// or use simple task delay for this
|
||||
// vTaskDelay( pdMS_TO_TICKS(-age / 1000) );
|
||||
|
||||
i2s_custom_start(I2S_PORT);
|
||||
i2s_custom_start(I2S_NUM_0);
|
||||
|
||||
// get timer value so we can get the real age
|
||||
timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &timer_val);
|
||||
@@ -987,7 +1115,7 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
if (size != 0) {
|
||||
do {
|
||||
written = 0;
|
||||
if (i2s_custom_write(I2S_PORT, p_payload, (size_t)size, &written,
|
||||
if (i2s_custom_write(I2S_NUM_0, p_payload, (size_t)size, &written,
|
||||
portMAX_DELAY) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_playback_task: I2S write error");
|
||||
}
|
||||
@@ -1042,7 +1170,7 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
// DMA buffer has a size of 1 chunk (e.g. 20ms) so next chunk
|
||||
// we get from queue will be -20ms
|
||||
const int64_t maxOffset = 50; //µs, softsync 1
|
||||
const int64_t hardResyncThreshold = 1000; //µs, hard sync
|
||||
const int64_t hardResyncThreshold = 3000; //µs, hard sync
|
||||
|
||||
avg = MEDIANFILTER_Insert(&shortMedianFilter, age + (-age_expect));
|
||||
if (MEDIANFILTER_isFull(&shortMedianFilter) == 0) {
|
||||
@@ -1085,7 +1213,7 @@ static void snapcast_sync_task(void *pvParameters) {
|
||||
|
||||
do {
|
||||
written = 0;
|
||||
if (i2s_custom_write(I2S_PORT, p_payload, (size_t)size, &written,
|
||||
if (i2s_custom_write(I2S_NUM_0, p_payload, (size_t)size, &written,
|
||||
portMAX_DELAY) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "i2s_playback_task: I2S write error");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
menu "Wifi Configuration"
|
||||
config ENABLE_WIFI_PROVISIONING
|
||||
bool "enable WiFi profisioning"
|
||||
bool "enable WiFi provisioning"
|
||||
default n
|
||||
help
|
||||
Enable WiFi provisioning so espressif APP can be used to provison WiFi credentials
|
||||
|
||||
165
main/main.c
165
main/main.c
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
@@ -61,6 +62,8 @@ xTaskHandle t_http_get_task;
|
||||
|
||||
xQueueHandle prot_queue;
|
||||
|
||||
static snapcastSetting_t snapcastSetting;
|
||||
|
||||
volatile int64_t clientDacLatency = 0;
|
||||
uint32_t buffer_ms = 400;
|
||||
uint8_t muteCH[4] = {0};
|
||||
@@ -83,13 +86,9 @@ int timeval_subtract(struct timeval *result, struct timeval *x,
|
||||
/* Logging tag */
|
||||
static const char *TAG = "SNAPCAST";
|
||||
|
||||
static char buff[SNAPCAST_BUFF_LEN];
|
||||
|
||||
extern char mac_address[18];
|
||||
extern EventGroupHandle_t s_wifi_event_group;
|
||||
|
||||
typedef enum codec_type_e { NONE, PCM, FLAC, OGG, OPUS } codec_type_t;
|
||||
|
||||
static QueueHandle_t playerChunkQueueHandle;
|
||||
SemaphoreHandle_t timeSyncSemaphoreHandle = NULL;
|
||||
|
||||
@@ -118,20 +117,21 @@ static void http_get_task(void *pvParameters) {
|
||||
char *start;
|
||||
int sock = -1;
|
||||
char base_message_serialized[BASE_MESSAGE_SIZE];
|
||||
char time_message_serialized[TIME_MESSAGE_SIZE];
|
||||
char *hello_message_serialized = NULL;
|
||||
int result, size, id_counter;
|
||||
struct timeval now, trx, tdif, ttx;
|
||||
time_message_t time_message;
|
||||
struct timeval tmpDiffToServer;
|
||||
struct timeval lastTimeSync = {0, 0};
|
||||
wire_chunk_message_t wire_chunk_message_last = {{0, 0}, 0, NULL};
|
||||
tv_t wire_chunk_last_timestamp = {0, 0};
|
||||
esp_timer_handle_t timeSyncMessageTimer = NULL;
|
||||
const esp_timer_create_args_t tSyncArgs = {.callback = &time_sync_msg_cb,
|
||||
.name = "tSyncMsg"};
|
||||
int16_t frameSize = 960; // 960*2: 20ms, 960*1: 10ms
|
||||
int16_t *audio = NULL;
|
||||
int16_t pcm_size = 120;
|
||||
uint16_t channels = CHANNELS;
|
||||
uint16_t channels;
|
||||
esp_err_t err = 0;
|
||||
codec_header_message_t codec_header_message;
|
||||
server_settings_message_t server_settings_message;
|
||||
@@ -141,6 +141,7 @@ static void http_get_task(void *pvParameters) {
|
||||
mdns_result_t *r;
|
||||
OpusDecoder *opusDecoder = NULL;
|
||||
codec_type_t codec = NONE;
|
||||
bool chunkDurationDetected;
|
||||
|
||||
// create a timer to send time sync messages every x µs
|
||||
esp_timer_create(&tSyncArgs, &timeSyncMessageTimer);
|
||||
@@ -237,6 +238,7 @@ static void http_get_task(void *pvParameters) {
|
||||
}
|
||||
|
||||
received_header = false;
|
||||
chunkDurationDetected = false;
|
||||
|
||||
// init base message
|
||||
base_message.type = SNAPCAST_MESSAGE_HELLO;
|
||||
@@ -304,11 +306,21 @@ static void http_get_task(void *pvParameters) {
|
||||
free(hello_message_serialized);
|
||||
hello_message_serialized = NULL;
|
||||
|
||||
// init default setting
|
||||
snapcastSetting.buffer_ms = 0;
|
||||
snapcastSetting.codec = NONE;
|
||||
snapcastSetting.bits = 0;
|
||||
snapcastSetting.channels = 0;
|
||||
snapcastSetting.sampleRate = 0;
|
||||
snapcastSetting.chunkDuration_ms = 0;
|
||||
snapcastSetting.volume = 0;
|
||||
snapcastSetting.muted = false;
|
||||
|
||||
for (;;) {
|
||||
size = 0;
|
||||
result = 0;
|
||||
while (size < BASE_MESSAGE_SIZE) {
|
||||
result = recv(sock, &(buff[size]), BASE_MESSAGE_SIZE - size, 0);
|
||||
result = recv(sock, &(base_message_serialized[size]), BASE_MESSAGE_SIZE - size, 0);
|
||||
if (result < 0) {
|
||||
break;
|
||||
}
|
||||
@@ -334,7 +346,7 @@ static void http_get_task(void *pvParameters) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result = base_message_deserialize(&base_message, buff, size);
|
||||
result = base_message_deserialize(&base_message, base_message_serialized, size);
|
||||
if (result) {
|
||||
ESP_LOGW(TAG, "Failed to read base message: %d", result);
|
||||
continue;
|
||||
@@ -348,26 +360,18 @@ static void http_get_task(void *pvParameters) {
|
||||
// base_message.received.sec,
|
||||
// base_message.received.usec );
|
||||
|
||||
start = buff;
|
||||
// TODO: ensure this buffer is freed before task gets deleted
|
||||
size = 0;
|
||||
char *typedMsg = malloc(sizeof(char) * base_message.size);
|
||||
if (typedMsg == NULL) {
|
||||
ESP_LOGE(TAG, "Couldn't get memory for typed message");
|
||||
|
||||
// TODO: dynamically allocate memory for the next read!!!
|
||||
// generate an error for now if we try to read more than
|
||||
// SNAPCAST_BUFF_LEN in next lines
|
||||
if (base_message.size > SNAPCAST_BUFF_LEN) {
|
||||
ESP_LOGE(TAG, "base_message.size too big %d", base_message.size);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
start = typedMsg;
|
||||
|
||||
while (size < base_message.size) {
|
||||
if (size >= SNAPCAST_BUFF_LEN) {
|
||||
ESP_LOGE(TAG, "Index too high");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
result = recv(sock, &(buff[size]), base_message.size - size, 0);
|
||||
result = recv(sock, &(start[size]), base_message.size - size, 0);
|
||||
if (result < 0) {
|
||||
ESP_LOGW(TAG, "Failed to read from server: %d", result);
|
||||
|
||||
@@ -426,8 +430,28 @@ static void http_get_task(void *pvParameters) {
|
||||
codec_header_message.codec);
|
||||
|
||||
codec = OPUS;
|
||||
|
||||
snapcastSetting.codec = codec;
|
||||
snapcastSetting.bits = bits;
|
||||
snapcastSetting.channels = channels;
|
||||
snapcastSetting.sampleRate = rate;
|
||||
} else if (strcmp(codec_header_message.codec, "pcm") == 0) {
|
||||
codec = PCM;
|
||||
|
||||
memcpy(&channels, start + 22, sizeof(channels));
|
||||
uint32_t rate;
|
||||
memcpy(&rate, start + 24, sizeof(rate));
|
||||
uint16_t bits;
|
||||
memcpy(&bits, start + 34, sizeof(bits));
|
||||
|
||||
ESP_LOGI(TAG, "%s sampleformat: %d:%d:%d",
|
||||
codec_header_message.codec, rate, bits, channels);
|
||||
|
||||
snapcastSetting.codec = codec;
|
||||
snapcastSetting.bits = bits;
|
||||
snapcastSetting.channels = channels;
|
||||
snapcastSetting.sampleRate = rate;
|
||||
|
||||
} else {
|
||||
codec = NONE;
|
||||
|
||||
@@ -455,6 +479,10 @@ static void http_get_task(void *pvParameters) {
|
||||
|
||||
case SNAPCAST_MESSAGE_WIRE_CHUNK: {
|
||||
if (!received_header) {
|
||||
if (typedMsg != NULL) {
|
||||
free(typedMsg);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -474,32 +502,6 @@ static void http_get_task(void *pvParameters) {
|
||||
// wire_chunk_message.timestamp.sec,
|
||||
// wire_chunk_message.timestamp.usec);
|
||||
|
||||
// TODO: detect chunk duration dynamically and allocate buffers
|
||||
// accordingly.
|
||||
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);
|
||||
wire_chunk_message_last.timestamp = wire_chunk_message.timestamp;
|
||||
// ESP_LOGI(TAG, "chunk duration %ld.%06ld", tv_d3.tv_sec,
|
||||
// tv_d3.tv_usec);
|
||||
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);
|
||||
|
||||
wire_chunk_message_free(&wire_chunk_message);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// store chunk's timestamp, decoder callback
|
||||
// will need it later
|
||||
tv_t timestamp;
|
||||
@@ -512,11 +514,11 @@ static void http_get_task(void *pvParameters) {
|
||||
if (audio == NULL) {
|
||||
#if CONFIG_USE_PSRAM
|
||||
audio = (int16_t *)heap_caps_malloc(
|
||||
frameSize * CHANNELS * (BITS_PER_SAMPLE / 8),
|
||||
frameSize * snapcastSetting.channels * (snapcastSetting.bits / 8),
|
||||
MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); // 960*2: 20ms, 960*1: 10ms
|
||||
#else
|
||||
audio = (int16_t *)malloc(
|
||||
frameSize * CHANNELS * (BITS_PER_SAMPLE / 8)); // 960*2: 20ms, 960*1: 10ms
|
||||
frameSize * snapcastSetting.channels * (snapcastSetting.bits / 8)); // 960*2: 20ms, 960*1: 10ms
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -536,12 +538,12 @@ static void http_get_task(void *pvParameters) {
|
||||
// 960*2: 20ms, 960*1: 10ms
|
||||
#if CONFIG_USE_PSRAM
|
||||
audio = (int16_t *)heap_caps_realloc(
|
||||
audio, pcm_size * CHANNELS * (BITS_PER_SAMPLE / 8), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); // 2 channels + 2 Byte per sample == int32_t
|
||||
audio, pcm_size * snapcastSetting.channels * (snapcastSetting.bits / 8), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); // 2 channels + 2 Byte per sample == int32_t
|
||||
#else
|
||||
audio = (int16_t *)realloc(
|
||||
audio, pcm_size * CHANNELS * (BITS_PER_SAMPLE / 8));
|
||||
audio, pcm_size * snapcastSetting.channels * (snapcastSetting.bits / 8));
|
||||
// audio = (int16_t *)heap_caps_realloc(
|
||||
// (int32_t *)audio, frameSize * CHANNELS * (BITS_PER_SAMPLE / 8), MALLOC_CAP_32BIT); // 960*2: 20ms, 960*1: 10ms
|
||||
// (int32_t *)audio, frameSize * CHANNELS * (BITS_PER_SAMPLE / 8), MALLOC_CAP_32BIT);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -562,11 +564,25 @@ static void http_get_task(void *pvParameters) {
|
||||
} else {
|
||||
wire_chunk_message_t pcm_chunk_message;
|
||||
|
||||
pcm_chunk_message.size = frame_size * CHANNELS * (BITS_PER_SAMPLE / 8);
|
||||
pcm_chunk_message.size = frame_size * snapcastSetting.channels * (snapcastSetting.bits / 8);
|
||||
pcm_chunk_message.payload = audio;
|
||||
pcm_chunk_message.timestamp = timestamp;
|
||||
|
||||
snapcastSetting.chunkDuration_ms = (1000UL * pcm_chunk_message.size) / (uint32_t)(snapcastSetting.channels * (snapcastSetting.bits / 8)) / snapcastSetting.sampleRate;
|
||||
if (player_send_snapcast_setting(snapcastSetting) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to notify sync task about codec. Did you init player?");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if (snapcastSetting.chunkDuration_ms > 30) {
|
||||
// ESP_LOGE(TAG, "We can't get that big chunks on this platform. RAM is a scarce good!");
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
|
||||
#if CONFIG_USE_DSP_PROCESSOR
|
||||
dsp_setup_flow(500, snapcastSetting.sampleRate, snapcastSetting.chunkDuration_ms);
|
||||
dsp_processor(pcm_chunk_message.payload,
|
||||
pcm_chunk_message.size, dspFlow);
|
||||
#endif
|
||||
@@ -580,16 +596,14 @@ static void http_get_task(void *pvParameters) {
|
||||
|
||||
case PCM: {
|
||||
wire_chunk_message_t pcm_chunk_message;
|
||||
uint16_t len = (CONFIG_PCM_SAMPLE_RATE * CONFIG_WIRE_CHUNK_DURATION_MS / 1000);
|
||||
|
||||
if (audio == NULL) {
|
||||
#if CONFIG_USE_PSRAM
|
||||
audio = (int16_t *)heap_caps_malloc(
|
||||
len * CHANNELS * (BITS_PER_SAMPLE / 8),
|
||||
pcm_chunk_message.size * sizeof(char),
|
||||
MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); // 960*2: 20ms, 960*1: 10ms
|
||||
#else
|
||||
audio = (int16_t *)malloc(
|
||||
len * CHANNELS * (BITS_PER_SAMPLE / 8)); // 960*2: 20ms, 960*1: 10ms
|
||||
audio = (int16_t *)malloc(pcm_chunk_message.size * sizeof(char));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -608,7 +622,15 @@ static void http_get_task(void *pvParameters) {
|
||||
memcpy(pcm_chunk_message.payload, start,
|
||||
pcm_chunk_message.size);
|
||||
|
||||
snapcastSetting.chunkDuration_ms = (1000UL * pcm_chunk_message.size) / (uint32_t)(snapcastSetting.channels * (snapcastSetting.bits / 8)) / snapcastSetting.sampleRate;
|
||||
if (player_send_snapcast_setting(snapcastSetting) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to notify sync task about codec. Did you init player?");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if CONFIG_USE_DSP_PROCESSOR
|
||||
dsp_setup_flow(500, snapcastSetting.sampleRate, snapcastSetting.chunkDuration_ms);
|
||||
dsp_processor(pcm_chunk_message.payload,
|
||||
pcm_chunk_message.size, dspFlow);
|
||||
#endif
|
||||
@@ -622,6 +644,8 @@ static void http_get_task(void *pvParameters) {
|
||||
default: {
|
||||
ESP_LOGE(TAG, "Decoder not supported");
|
||||
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -658,13 +682,18 @@ static void http_get_task(void *pvParameters) {
|
||||
muteCH[2] = server_settings_message.muted;
|
||||
muteCH[3] = server_settings_message.muted;
|
||||
|
||||
snapcastSetting.buffer_ms = server_settings_message.buffer_ms;
|
||||
snapcastSetting.muted = server_settings_message.muted;
|
||||
snapcastSetting.volume = server_settings_message.volume;
|
||||
|
||||
// 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);
|
||||
|
||||
if (player_notify_buffer_ms(buffer_ms) < 0) {
|
||||
//if (player_notify_buffer_ms(buffer_ms) < 0) {
|
||||
if (player_send_snapcast_setting(snapcastSetting) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to notify sync task. Did you init player?");
|
||||
|
||||
return;
|
||||
@@ -780,6 +809,10 @@ static void http_get_task(void *pvParameters) {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (typedMsg != NULL) {
|
||||
free(typedMsg);
|
||||
}
|
||||
}
|
||||
|
||||
if (received_header == true) {
|
||||
@@ -810,7 +843,7 @@ static void http_get_task(void *pvParameters) {
|
||||
memset(&time_message, 0, sizeof(time_message));
|
||||
|
||||
result =
|
||||
time_message_serialize(&time_message, buff, SNAPCAST_BUFF_LEN);
|
||||
time_message_serialize(&time_message, time_message_serialized, TIME_MESSAGE_SIZE);
|
||||
if (result) {
|
||||
ESP_LOGI(TAG, "Failed to serialize time message\r\b");
|
||||
continue;
|
||||
@@ -827,7 +860,7 @@ static void http_get_task(void *pvParameters) {
|
||||
break; // stop for(;;) will try to reconnect then
|
||||
}
|
||||
|
||||
result = send(sock, buff, TIME_MESSAGE_SIZE, 0);
|
||||
result = send(sock, time_message_serialized, TIME_MESSAGE_SIZE, 0);
|
||||
if (result < 0) {
|
||||
ESP_LOGW(TAG, "error writing timesync msg to socket: %s",
|
||||
strerror(errno));
|
||||
@@ -856,9 +889,11 @@ void app_main(void) {
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
// esp_log_level_set("c_I2S", ESP_LOG_NONE); //
|
||||
esp_log_level_set("HEADPHONE", ESP_LOG_NONE); // if enabled these cause a timer srv stack overflow
|
||||
esp_log_level_set("gpio", ESP_LOG_NONE); //
|
||||
|
||||
|
||||
esp_timer_init();
|
||||
|
||||
ESP_LOGI(TAG, "Start codec chip");
|
||||
@@ -870,9 +905,9 @@ void app_main(void) {
|
||||
i2s_mclk_gpio_select(0, 0);
|
||||
// setup_ma120();
|
||||
|
||||
#if CONFIG_USE_DSP_PROCESSOR
|
||||
dsp_setup_flow(500, SAMPLE_RATE);
|
||||
#endif
|
||||
#if CONFIG_USE_DSP_PROCESSOR
|
||||
dsp_setup_flow(500, 44100, 20); // init with default value
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "init player");
|
||||
playerChunkQueueHandle = init_player();
|
||||
|
||||
@@ -283,15 +283,11 @@ CONFIG_ESP_DISPATCHER_DELEGATE_STACK_SIZE=4096
|
||||
# end of ADF Features
|
||||
|
||||
#
|
||||
# ESP32 audio buffer and I2S pin config
|
||||
# ESP32 audio buffer and I2S config
|
||||
#
|
||||
CONFIG_USE_DSP_PROCESSOR=y
|
||||
CONFIG_BITS_PER_SAMPLE=16
|
||||
CONFIG_CHANNELS=2
|
||||
CONFIG_PCM_SAMPLE_RATE=48000
|
||||
CONFIG_WIRE_CHUNK_DURATION_MS=20
|
||||
CONFIG_USE_BIQUAD_ASM=y
|
||||
# end of ESP32 audio buffer and I2S pin config
|
||||
# end of ESP32 audio buffer and I2S config
|
||||
|
||||
#
|
||||
# SNTP Configuration
|
||||
|
||||
@@ -283,15 +283,12 @@ CONFIG_ESP_DISPATCHER_DELEGATE_STACK_SIZE=4096
|
||||
# end of ADF Features
|
||||
|
||||
#
|
||||
# ESP32 audio buffer and I2S pin config
|
||||
# ESP32 audio buffer and I2S config
|
||||
#
|
||||
CONFIG_USE_PSRAM=y
|
||||
CONFIG_USE_DSP_PROCESSOR=y
|
||||
CONFIG_BITS_PER_SAMPLE=16
|
||||
CONFIG_CHANNELS=2
|
||||
CONFIG_PCM_SAMPLE_RATE=48000
|
||||
CONFIG_WIRE_CHUNK_DURATION_MS=20
|
||||
CONFIG_USE_BIQUAD_ASM=y
|
||||
# end of ESP32 audio buffer and I2S pin config
|
||||
# end of ESP32 audio buffer and I2S config
|
||||
|
||||
#
|
||||
# SNTP Configuration
|
||||
@@ -476,6 +473,7 @@ CONFIG_ESP_TLS_USING_MBEDTLS=y
|
||||
#
|
||||
# ESP32-specific
|
||||
#
|
||||
CONFIG_ESP32_ECO3_CACHE_LOCK_FIX=y
|
||||
CONFIG_ESP32_REV_MIN_0=y
|
||||
# CONFIG_ESP32_REV_MIN_1 is not set
|
||||
# CONFIG_ESP32_REV_MIN_2 is not set
|
||||
@@ -486,7 +484,68 @@ CONFIG_ESP32_DPORT_WORKAROUND=y
|
||||
# CONFIG_ESP32_DEFAULT_CPU_FREQ_160 is not set
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
|
||||
# CONFIG_ESP32_SPIRAM_SUPPORT is not set
|
||||
CONFIG_ESP32_SPIRAM_SUPPORT=y
|
||||
|
||||
#
|
||||
# SPI RAM config
|
||||
#
|
||||
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||
CONFIG_SPIRAM_SIZE=-1
|
||||
# CONFIG_SPIRAM_SPEED_40M is not set
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_BOOT_INIT=y
|
||||
# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set
|
||||
# CONFIG_SPIRAM_USE_MEMMAP is not set
|
||||
CONFIG_SPIRAM_USE_CAPS_ALLOC=y
|
||||
# CONFIG_SPIRAM_USE_MALLOC is not set
|
||||
CONFIG_SPIRAM_MEMTEST=y
|
||||
# CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP is not set
|
||||
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
|
||||
CONFIG_SPIRAM_CACHE_WORKAROUND=y
|
||||
|
||||
#
|
||||
# SPIRAM cache workaround debugging
|
||||
#
|
||||
CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_MEMW=y
|
||||
# CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_DUPLDST is not set
|
||||
# CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_NOPS is not set
|
||||
# end of SPIRAM cache workaround debugging
|
||||
|
||||
CONFIG_SPIRAM_BANKSWITCH_ENABLE=y
|
||||
CONFIG_SPIRAM_BANKSWITCH_RESERVE=8
|
||||
# CONFIG_SPIRAM_OCCUPY_HSPI_HOST is not set
|
||||
CONFIG_SPIRAM_OCCUPY_VSPI_HOST=y
|
||||
# CONFIG_SPIRAM_OCCUPY_NO_HOST is not set
|
||||
|
||||
#
|
||||
# PSRAM clock and cs IO for ESP32-DOWD
|
||||
#
|
||||
CONFIG_D0WD_PSRAM_CLK_IO=17
|
||||
CONFIG_D0WD_PSRAM_CS_IO=16
|
||||
# end of PSRAM clock and cs IO for ESP32-DOWD
|
||||
|
||||
#
|
||||
# PSRAM clock and cs IO for ESP32-D2WD
|
||||
#
|
||||
CONFIG_D2WD_PSRAM_CLK_IO=9
|
||||
CONFIG_D2WD_PSRAM_CS_IO=10
|
||||
# end of PSRAM clock and cs IO for ESP32-D2WD
|
||||
|
||||
#
|
||||
# PSRAM clock and cs IO for ESP32-PICO
|
||||
#
|
||||
CONFIG_PICO_PSRAM_CS_IO=10
|
||||
# end of PSRAM clock and cs IO for ESP32-PICO
|
||||
|
||||
# CONFIG_SPIRAM_CUSTOM_SPIWP_SD3_PIN is not set
|
||||
CONFIG_SPIRAM_SPIWP_SD3_PIN=7
|
||||
# CONFIG_SPIRAM_2T_MODE is not set
|
||||
# end of SPI RAM config
|
||||
|
||||
# CONFIG_ESP32_TRAX is not set
|
||||
CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0
|
||||
# CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO is not set
|
||||
@@ -651,7 +710,6 @@ CONFIG_ESP_NETIF_TCPIP_ADAPTER_COMPATIBLE_LAYER=y
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y
|
||||
# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set
|
||||
# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set
|
||||
CONFIG_ESP_SYSTEM_PD_FLASH=y
|
||||
|
||||
#
|
||||
# Memory protection
|
||||
@@ -673,20 +731,22 @@ CONFIG_ESP_TIMER_IMPL_TG0_LAC=y
|
||||
#
|
||||
# Wi-Fi
|
||||
#
|
||||
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=2
|
||||
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=12
|
||||
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=8
|
||||
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32
|
||||
# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set
|
||||
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
|
||||
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1
|
||||
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=6
|
||||
CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32
|
||||
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
|
||||
# CONFIG_ESP32_WIFI_CSI_ENABLED is not set
|
||||
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
|
||||
CONFIG_ESP32_WIFI_TX_BA_WIN=2
|
||||
CONFIG_ESP32_WIFI_TX_BA_WIN=4
|
||||
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
|
||||
CONFIG_ESP32_WIFI_RX_BA_WIN=2
|
||||
CONFIG_ESP32_WIFI_RX_BA_WIN=4
|
||||
# CONFIG_ESP32_WIFI_AMSDU_TX_ENABLED is not set
|
||||
CONFIG_ESP32_WIFI_NVS_ENABLED=y
|
||||
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y
|
||||
# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set
|
||||
# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 is not set
|
||||
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1=y
|
||||
CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752
|
||||
CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
|
||||
# CONFIG_ESP32_WIFI_DEBUG_LOG_ENABLE is not set
|
||||
@@ -746,6 +806,7 @@ CONFIG_FATFS_LFN_NONE=y
|
||||
CONFIG_FATFS_FS_LOCK=0
|
||||
CONFIG_FATFS_TIMEOUT_MS=10000
|
||||
CONFIG_FATFS_PER_FILE_CACHE=y
|
||||
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
|
||||
# CONFIG_FATFS_USE_FASTSEEK is not set
|
||||
# end of FAT Filesystem support
|
||||
|
||||
@@ -785,7 +846,7 @@ CONFIG_FMB_TIMER_INDEX=0
|
||||
CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
|
||||
CONFIG_FREERTOS_CORETIMER_0=y
|
||||
# CONFIG_FREERTOS_CORETIMER_1 is not set
|
||||
CONFIG_FREERTOS_HZ=100
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
|
||||
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set
|
||||
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set
|
||||
@@ -862,7 +923,7 @@ CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
|
||||
# CONFIG_LWIP_L2_TO_L3_COPY is not set
|
||||
# CONFIG_LWIP_IRAM_OPTIMIZATION is not set
|
||||
CONFIG_LWIP_TIMERS_ONDEMAND=y
|
||||
CONFIG_LWIP_MAX_SOCKETS=3
|
||||
CONFIG_LWIP_MAX_SOCKETS=6
|
||||
# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
|
||||
# CONFIG_LWIP_SO_LINGER is not set
|
||||
CONFIG_LWIP_SO_REUSE=y
|
||||
@@ -986,6 +1047,7 @@ CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y
|
||||
# mbedTLS
|
||||
#
|
||||
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
|
||||
@@ -1367,7 +1429,8 @@ CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_ACL_CONN_EFF=0
|
||||
CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_SYNC_CONN_EFF=0
|
||||
CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE=0
|
||||
CONFIG_ADC2_DISABLE_DAC=y
|
||||
# CONFIG_SPIRAM_SUPPORT is not set
|
||||
CONFIG_SPIRAM_SUPPORT=y
|
||||
# CONFIG_WIFI_LWIP_ALLOCATION_FROM_SPIRAM_FIRST is not set
|
||||
CONFIG_TRACEMEM_RESERVE_DRAM=0x0
|
||||
# CONFIG_TWO_UNIVERSAL_MAC_ADDRESS is not set
|
||||
CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y
|
||||
|
||||
1474
sdkconfig_NO_SPI_RAM
Normal file
1474
sdkconfig_NO_SPI_RAM
Normal file
File diff suppressed because it is too large
Load Diff
1544
sdkconfig_SPI_RAM
Normal file
1544
sdkconfig_SPI_RAM
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user