- add ringbuffer and dedicated i2s task

This commit is contained in:
Carlos
2021-05-11 05:44:56 +02:00
Unverified
parent 4dc35d1017
commit 736d47e9d1
6 changed files with 720 additions and 153 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "components/opus/opus"]
path = components/opus/opus
url = https://github.com/xiph/opus

View File

@@ -0,0 +1,20 @@
file(GLOB srcs "opus/src/*.c")
file(GLOB silk "opus/silk/*.c")
file(GLOB celt "opus/celt/*.c")
file(GLOB fixed "opus/slik/fixed/*.c")
file(GLOB float "opus/slik/float/*.c")
idf_component_register(SRCS "${srcs}" "${silk}" "${celt}" "${fixed}" "${float}"
INCLUDE_DIRS .
"opus/include"
"opus/silk"
"opus/silk/fixed"
"opus/silk/float"
"opus/celt"
)
target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DHAVE_CONFIG_H")

View File

@@ -0,0 +1,12 @@
#
# Main Makefile. This is basically the same as a component makefile.
#
# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default,
# this will take the sources in the src/ directory, compile them and link them into
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#
COMPONENT_SRCDIRS := opus/src opus/silk opus/silk/fixed opus/celt
COMPONENT_ADD_INCLUDEDIRS := . opus/include opus/silk opus/silk/fixed opus/celt
CFLAGS += -DHAVE_CONFIG_H

208
components/opus/config.h Normal file
View File

@@ -0,0 +1,208 @@
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
/* Get CPU Info by asm method */
/* #undef CPU_INFO_BY_ASM */
/* Get CPU Info by c method */
/* #undef CPU_INFO_BY_C */
/* Custom modes */
/* #undef CUSTOM_MODES */
/* Do not build the float API */
//#define DISABLE_FLOAT_API 1
/* Assertions */
/* #undef ENABLE_ASSERTIONS */
/* Ambisonics Support */
/* #undef ENABLE_EXPERIMENTAL_AMBISONICS */
/* Enable bitstream changes from draft-ietf-codec-opus-update */
/* #undef ENABLE_UPDATE_DRAFT */
/* Debug fixed-point implementation */
/* #undef FIXED_DEBUG */
/* Compile as fixed-point (for machines without a fast enough FPU) */
#define FIXED_POINT 1
/* Float approximations */
/* #undef FLOAT_APPROX */
/* Fuzzing */
/* #undef FUZZING */
/* Define to 1 if you have the <alloca.h> header file. */
/* #undef HAVE_ALLOCA_H */
/* NE10 library is installed on host. Make sure it is on target! */
/* #undef HAVE_ARM_NE10 */
/* Define to 1 if you have the <dlfcn.h> header file. */
/* #undef HAVE_DLFCN_H */
#define HAVE_DLFCN_H 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Define to 1 if you have the `lrint' function. */
/* #undef HAVE_LRINT */
#define HAVE_LRINT 1
/* Define to 1 if you have the `lrintf' function. */
/* #undef HAVE_LRINTF */
#define HAVE_LRINTF 1
/* Define to 1 if you have the <memory.h> header file. */
/* #undef HAVE_MEMORY_H */
#define HAVE_MEMORY_H 1
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to 1 if you have the `__malloc_hook' function. */
/* #undef HAVE___MALLOC_HOOK */
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#define LT_OBJDIR ".libs/"
/* Make use of ARM asm optimization */
/* #undef OPUS_ARM_ASM */
/* Use generic ARMv4 inline asm optimizations */
/* #undef OPUS_ARM_INLINE_ASM */
/* Use ARMv5E inline asm optimizations */
/* #undef OPUS_ARM_INLINE_EDSP */
/* Use ARMv6 inline asm optimizations */
/* #undef OPUS_ARM_INLINE_MEDIA */
/* Use ARM NEON inline asm optimizations */
/* #undef OPUS_ARM_INLINE_NEON */
/* Define if assembler supports EDSP instructions */
/* #undef OPUS_ARM_MAY_HAVE_EDSP */
/* Define if assembler supports ARMv6 media instructions */
/* #undef OPUS_ARM_MAY_HAVE_MEDIA */
/* Define if compiler supports NEON instructions */
/* #undef OPUS_ARM_MAY_HAVE_NEON */
/* Compiler supports ARMv7/Aarch64 Neon Intrinsics */
/* #undef OPUS_ARM_MAY_HAVE_NEON_INTR */
/* Define if binary requires Aarch64 Neon Intrinsics */
/* #undef OPUS_ARM_PRESUME_AARCH64_NEON_INTR */
/* Define if binary requires EDSP instruction support */
/* #undef OPUS_ARM_PRESUME_EDSP */
/* Define if binary requires ARMv6 media instruction support */
/* #undef OPUS_ARM_PRESUME_MEDIA */
/* Define if binary requires NEON instruction support */
/* #undef OPUS_ARM_PRESUME_NEON */
/* Define if binary requires NEON intrinsics support */
/* #undef OPUS_ARM_PRESUME_NEON_INTR */
/* This is a build of OPUS */
#define OPUS_BUILD /**/
/* Run bit-exactness checks between optimized and c implementations */
/* #undef OPUS_CHECK_ASM */
/* Use run-time CPU capabilities detection */
/* #undef OPUS_HAVE_RTCD */
/* Compiler supports X86 AVX Intrinsics */
/* #undef OPUS_X86_MAY_HAVE_AVX */
/* Compiler supports X86 SSE Intrinsics */
/* #undef OPUS_X86_MAY_HAVE_SSE */
/* Compiler supports X86 SSE2 Intrinsics */
/* #undef OPUS_X86_MAY_HAVE_SSE2 */
/* Compiler supports X86 SSE4.1 Intrinsics */
/* #undef OPUS_X86_MAY_HAVE_SSE4_1 */
/* Define if binary requires AVX intrinsics support */
/* #undef OPUS_X86_PRESUME_AVX */
/* Define if binary requires SSE intrinsics support */
/* #undef OPUS_X86_PRESUME_SSE */
/* Define if binary requires SSE2 intrinsics support */
/* #undef OPUS_X86_PRESUME_SSE2 */
/* Define if binary requires SSE4.1 intrinsics support */
/* #undef OPUS_X86_PRESUME_SSE4_1 */
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "opus@xiph.org"
/* Define to the full name of this package. */
#define PACKAGE_NAME "opus"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "opus 1.3.1"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "opus"
/* Define to the home page for this package. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
#define PACKAGE_VERSION "1.3.1"
/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1
/* Make use of alloca */
/* #undef USE_ALLOCA */
/* Use C99 variable-size arrays */
#define VAR_ARRAYS 1
/* Define to empty if `const' does not conform to ANSI C. */
/* #undef const */
/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus
/* #undef inline */
#endif
/* Define to the equivalent of the C99 'restrict' keyword, or to
nothing if this is not supported. Do not define if restrict is
supported directly. */
#define restrict __restrict
/* Work around a bug in Sun C++: it does not support _Restrict or
__restrict__, even though the corresponding Sun C compiler ends up with
"#define restrict _Restrict" or "#define restrict __restrict__" in the
previous line. Perhaps some future version of Sun C++ will work with
restrict; if so, hopefully it defines __RESTRICT like Sun C does. */
#if defined __SUNPRO_CC && !defined __RESTRICT
# define _Restrict
# define __restrict__
#endif

1
components/opus/opus Submodule

Submodule components/opus/opus added at 8aa7767207

View File

@@ -59,6 +59,8 @@
#define CHANNELS 2UL
#define BITS_PER_SAMPLE 16UL
const size_t chunkInBytes = (WIRE_CHUNK_DURATION_MS * SAMPLE_RATE * CHANNELS * (BITS_PER_SAMPLE / 8)) / 1000;
const char *VERSION_STRING = "0.1.0";
/**
@@ -93,14 +95,17 @@ xQueueHandle i2s_event_queue;
audio_pipeline_handle_t flacDecodePipeline;
audio_element_handle_t raw_stream_writer_to_decoder, decoder;
ringbuf_handle_t i2sRingBufferHandle = NULL;
uint64_t wirechnkCnt = 0;
uint64_t pcmchnkCnt = 0;
TaskHandle_t syncTaskHandle = NULL;
TaskHandle_t i2STaskHandle = NULL;
#define CONFIG_USE_SNTP 0
#define DAC_OUT_BUFFER_TIME_US 3000 // determined this by comparing a 180bpm metronome signal on a scope which is played by esp32 and ubuntu client
#define DAC_OUT_BUFFER_TIME_US 3000//-8000//2000 // determined this by comparing a 180bpm metronome signal on a scope which is played by esp32 and ubuntu client
// not sure why I need this though... And why it is so high. I'd expect something in the µs range??!
static const char *TAG = "SC";
@@ -121,7 +126,7 @@ char *codecString = NULL;
#define HTTP_TASK_PRIORITY 6
#define HTTP_TASK_CORE_ID tskNO_AFFINITY//0//tskNO_AFFINITY
#define I2S_TASK_PRIORITY 6//6//configMAX_PRIORITIES - 1
#define I2S_TASK_PRIORITY 8//6//configMAX_PRIORITIES - 1
#define I2S_TASK_CORE_ID tskNO_AFFINITY//1//tskNO_AFFINITY
#define FLAC_DECODER_PRIORITY 6
@@ -177,7 +182,7 @@ static int64_t latencyToServer = 0;
// shortBuffer_.setSize(100);
// miniBuffer_.setSize(20);
#define SHORT_BUFFER_LEN 59//99
#define SHORT_BUFFER_LEN 99
int64_t short_buffer[SHORT_BUFFER_LEN];
int64_t short_buffer_median[SHORT_BUFFER_LEN];
@@ -226,6 +231,9 @@ const int WIFI_FAIL_EVENT = BIT1;
static int s_retry_num = 0;
void i2s_playback_task(void *args);
// 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) {
@@ -942,9 +950,14 @@ static void snapcast_sync_task(void *pvParameters) {
int dir = 0;
i2s_event_t i2sEvent;
uint32_t i2sDmaBufferCnt = 0;
int writtenBytes, bytesAvailable;
ESP_LOGI(TAG, "started sync task");
// create ringbuffer for i2s
i2sRingBufferHandle = rb_create(chunkInBytes * 3, sizeof(char)); // can hold 2 chunks of audio
xTaskCreatePinnedToCore(i2s_playback_task, "i2s_playback_task", 8*1024, NULL, I2S_TASK_PRIORITY, &i2STaskHandle, I2S_TASK_CORE_ID);
tg0_timer_init(); // initialize initial sync timer
initialSync = 0;
@@ -971,54 +984,80 @@ static void snapcast_sync_task(void *pvParameters) {
if (chnk == NULL) {
// ESP_LOGE(TAG, "msg waiting pcm %d ts %d", uxQueueMessagesWaiting(pcmChunkQueueHandle), uxQueueMessagesWaiting(timestampQueueHandle));
if ((initialSync == 0) && (i2sDmaBufferCnt == 0)) {
// TODO: use task notify from i2s task to unblock if we need data
//if ((initialSync == 0) && (i2sDmaBufferCnt == 0)) {
// bytesAvailable = rb_bytes_filled(i2sRingBufferHandle);
// if ((bytesAvailable >= 0) && (bytesAvailable <= 4608))
if (1)
{
if (initialSync == 1) {
// Wait to be notified how much has been transmitted by i2s task
xTaskNotifyWait( pdFALSE, // Don't clear bits on entry.
ULONG_MAX, // Clear all bits on exit.
&notifiedValue, // Stores the notified value.
portMAX_DELAY
);
// if (notifiedValue < chunkInBytes) {
// bytesAvailable = rb_bytes_filled(i2sRingBufferHandle);
// if (bytesAvailable > 0) {
// continue;
// }
// }
}
ret = xQueueReceive(pcmChunkQueueHandle, &chnk, pdMS_TO_TICKS(2000) );
if( ret != pdFAIL ) {
// ESP_LOGW(TAG, "got first pcm chunk");
}
}
else {
ret = xQueueReceive(i2s_event_queue, &i2sEvent, pdMS_TO_TICKS(24) );
if( ret != pdFAIL ) {
if (i2sEvent.type == I2S_EVENT_TX_DONE) {
// ESP_LOGI(TAG, "I2S_EVENT_TX_DONE, %u", i2sDmaBufferCnt);
if (i2sDmaBufferCnt > 0) {
i2sDmaBufferCnt--;
if ((initialSync == 0) && (i2sDmaBufferCnt == 0)) {
i2s_stop(i2s_cfg.i2s_port);
// ESP_LOGI(TAG, "Ringbuffer %d", bytesAvailable);
vTaskDelay(pdMS_TO_TICKS(1));
continue;
}
}
if ((i2sDmaBufferCnt % 2) == 0) {
ret = xQueueReceive(pcmChunkQueueHandle, &chnk, pdMS_TO_TICKS(10) );
if( ret != pdFAIL ) {
// ESP_LOGW(TAG, "got next pcm chunk");
}
else {
ESP_LOGW(TAG, "couldn't get pcm chunk %d %d", uxQueueMessagesWaiting(pcmChunkQueueHandle), uxQueueMessagesWaiting(timestampQueueHandle));
continue;
}
}
else {
// ESP_LOGW(TAG, "continue");
continue;
}
}
else {
ESP_LOGW(TAG, "i2s unexpected event");
continue;
}
}
else {
ESP_LOGW(TAG, "no i2s events");
continue;
}
continue;
// ret = xQueueReceive(i2s_event_queue, &i2sEvent, pdMS_TO_TICKS(24) );
// if( ret != pdFAIL ) {
// if (i2sEvent.type == I2S_EVENT_TX_DONE) {
//// ESP_LOGI(TAG, "I2S_EVENT_TX_DONE, %u", i2sDmaBufferCnt);
// if (i2sDmaBufferCnt > 0) {
// i2sDmaBufferCnt--;
// if ((initialSync == 0) && (i2sDmaBufferCnt == 0)) {
// i2s_stop(i2s_cfg.i2s_port);
//
// continue;
// }
// }
//
// if ((i2sDmaBufferCnt % 2) == 0) {
// ret = xQueueReceive(pcmChunkQueueHandle, &chnk, pdMS_TO_TICKS(10) );
// if( ret != pdFAIL ) {
//// ESP_LOGW(TAG, "got next pcm chunk");
// }
// else {
// ESP_LOGW(TAG, "couldn't get pcm chunk %d %d", uxQueueMessagesWaiting(pcmChunkQueueHandle), uxQueueMessagesWaiting(timestampQueueHandle));
//
// continue;
// }
// }
// else {
//// ESP_LOGW(TAG, "continue");
//
// continue;
// }
// }
// else {
// ESP_LOGW(TAG, "i2s unexpected event");
//
// continue;
// }
// }
// else {
// ESP_LOGW(TAG, "no i2s events");
//
// continue;
// }
}
}
else {
@@ -1090,17 +1129,17 @@ static void snapcast_sync_task(void *pvParameters) {
size = chnk->size;
}
if ((initialSync == 0) && (i2sDmaBufferCnt > 0)) {
ESP_LOGW(TAG, "waiting for i2s to empty %u", i2sDmaBufferCnt);
if (chnk != NULL) {
free(chnk->payload);
free(chnk);
chnk = NULL;
}
continue;
}
// if ((initialSync == 0) && (i2sDmaBufferCnt > 0)) {
// ESP_LOGW(TAG, "waiting for i2s to empty %u", i2sDmaBufferCnt);
//
// if (chnk != NULL) {
// free(chnk->payload);
// free(chnk);
// chnk = NULL;
// }
//
// continue;
// }
if (age < 0) { // get initial sync using hardware timer
if (initialSync == 0) {
@@ -1118,78 +1157,149 @@ static void snapcast_sync_task(void *pvParameters) {
// p_payload = chnk->payload;
// size = chnk->size;
i2s_stop(i2s_cfg.i2s_port);
// i2s_stop(i2s_cfg.i2s_port);
// i2s_zero_dma_buffer(i2s_cfg.i2s_port);
xQueueReset(i2s_event_queue);
// xQueueReset(i2s_event_queue);
i2sDmaBufferCnt = 0;
size_t writtenBytes;
int err = i2s_write(i2s_cfg.i2s_port, p_payload, size, &writtenBytes, pdMS_TO_TICKS(2000));
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2S write error");
}
i2sDmaBufferCnt += 2;
// ESP_LOGE(TAG, "I2S written 1 %u / %u", writtenBytes, size);
size -= writtenBytes;
//p_payload += writtenBytes; // TODO: produces heap error???
if (size == 0) {
ESP_LOGI(TAG, "I2S can take more");
// wait for i2s Task to reinitialize
ret = xTaskNotifyWait( pdFALSE, // Don't clear bits on entry.
ULONG_MAX, // Clear all bits on exit.
&notifiedValue, // Stores the notified value.
0
);
if ((notifiedValue != 0) || (ret == pdFAIL)) {
if (chnk != NULL) {
free(chnk->payload);
free(chnk);
chnk = NULL;
}
continue;
}
// prefill ringbuffer with 2 chunks
// ESP_LOGW(TAG, "rb_write");
writtenBytes = rb_write(i2sRingBufferHandle, p_payload, size, pdMS_TO_TICKS(12));
size -= writtenBytes;
p_payload += writtenBytes;
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;
err = i2s_write(i2s_cfg.i2s_port, p_payload, size, &writtenBytes, pdMS_TO_TICKS(2000));
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2S write error");
}
// ESP_LOGE(TAG, "I2S written 2 %u / %u", writtenBytes, size);
i2sDmaBufferCnt += 2;
writtenBytes = rb_write(i2sRingBufferHandle, p_payload, size, pdMS_TO_TICKS(12));
size -= writtenBytes;
if (size != 0) {
ESP_LOGE(TAG, "I2S check DMA buffer sizes, 1 chunk should fit in two DMA buffers! %u, %u", size, writtenBytes);
}
p_payload += writtenBytes;
if (chnk != NULL) {
if ((chnk != NULL) && (size == 0)) {
free(chnk->payload);
free(chnk);
chnk = NULL;
}
//p_payload += writtenBytes; // TODO: produces heap error???
}
else {
ESP_LOGW(TAG, "I2S writing more not possible, couldn't get pcm chunk");
continue;
}
}
// i2sDmaBufferCnt = 0;
// size_t writtenBytes;
// int err = i2s_write(i2s_cfg.i2s_port, p_payload, size, &writtenBytes, pdMS_TO_TICKS(2000));
// if (err != ESP_OK) {
// ESP_LOGE(TAG, "I2S write error");
// }
// i2sDmaBufferCnt += 2;
// ESP_LOGE(TAG, "I2S written 1 %u / %u", writtenBytes, size);
// size -= writtenBytes;
// p_payload += writtenBytes;
// if (size == 0) {
// ESP_LOGI(TAG, "I2S can take more");
//
// if (chnk != NULL) {
// free(chnk->payload);
// free(chnk);
// chnk = NULL;
// }
//
// ret = xQueueReceive(pcmChunkQueueHandle, &chnk, portMAX_DELAY);
// if( ret != pdFAIL ) {
// p_payload = chnk->payload;
// size = chnk->size;
// err = i2s_write(i2s_cfg.i2s_port, p_payload, size, &writtenBytes, pdMS_TO_TICKS(2000));
// if (err != ESP_OK) {
// ESP_LOGE(TAG, "I2S write error");
// }
//
//// ESP_LOGE(TAG, "I2S written 2 %u / %u", writtenBytes, size);
//
// i2sDmaBufferCnt += 2;
//
// size -= writtenBytes;
// if (size != 0) {
// ESP_LOGE(TAG, "I2S check DMA buffer sizes, 1 chunk should fit in two DMA buffers! %u, %u", size, writtenBytes);
// }
//
// if (chnk != NULL) {
// free(chnk->payload);
// free(chnk);
// chnk = NULL;
// }
//
// //p_payload += writtenBytes; // TODO: produces heap error???
// }
// else {
// ESP_LOGW(TAG, "I2S writing more not possible, couldn't get pcm chunk");
//
// continue;
// }
// }
// // Notify the task in the task's notification value that we almost got initial sync soo it can preload dma
// xTaskNotify( i2STaskHandle,
// 2,
// eSetValueWithOverwrite);
// ensure no notifications are pending from i2s task
// xTaskNotifyWait( pdFALSE, // Don't clear bits on entry.
// ULONG_MAX, // Clear all bits on exit.
// &notifiedValue, // Stores the notified value.
// 0
// );
/*
tg0_timer1_start(-age - alarmValSub); // alarm a little earlier to account for context switch duration from freeRTOS, timer with 1µs ticks
vTaskDelay(pdMS_TO_TICKS(-age / 1000));
// 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);
xTaskNotifyWait( pdFALSE, // Don't clear bits on entry.
ULONG_MAX, // Clear all bits on exit.
&notifiedValue, // Stores the notified value.
0
);
age = (int64_t)timer_val - (-age); // timer with 1µs ticks
*/
//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
// Wait to be notified of an interrupt.
// Wait to be notified of a timer interrupt.
xTaskNotifyWait( pdFALSE, // Don't clear bits on entry.
ULONG_MAX, // Clear all bits on exit.
&notifiedValue, // Stores the notified value.
portMAX_DELAY
);
i2s_start(i2s_cfg.i2s_port);
// i2s_start(i2s_cfg.i2s_port);
// get timer value so we can get the real age
timer_get_counter_value(TIMER_GROUP_0, TIMER_1, &timer_val);
@@ -1199,6 +1309,8 @@ static void snapcast_sync_task(void *pvParameters) {
//age = ((int64_t)timer_val - (-age) * 10) / 10; // timer with 100ns ticks
age = (int64_t)timer_val - (-age); // timer with 1µs ticks
// TODO: try to get better initial sync using alarmValSub to alarm early,
// doesn't work with current style of loading i2s buffer early.
// if (((age < -11LL) || (age > 0)) && (initialSync == 0)) {
@@ -1234,7 +1346,15 @@ static void snapcast_sync_task(void *pvParameters) {
initialSync = 1;
// Notify the task in the task's notification value that we got initial sync
xTaskNotify( i2STaskHandle,
initialSync,
eSetValueWithOverwrite);
ESP_LOGI(TAG, "initial sync %lldus", age);
portYIELD(); // i2s task can start work now
// ESP_LOGW(TAG, "chunk %d %d", uxQueueMessagesWaiting(pcmChunkQueueHandle), uxQueueMessagesWaiting(timestampQueueHandle));
continue;
@@ -1256,13 +1376,20 @@ static void snapcast_sync_task(void *pvParameters) {
initialSync = 0;
alarmValSub = 0;
// Notify the task in the task's notification value that we lost initial sync
xTaskNotify( i2STaskHandle,
initialSync,
eSetValueWithOverwrite);
portYIELD(); // i2s task probably needs to reinitialize before we can continue
continue;
}
if (initialSync == 1) {
const uint8_t enableControlLoop = 1;
const int64_t age_expect = -24000;
const int64_t maxOffset = 100;
const int64_t age_expect = -48000;
const int64_t maxOffset = 500;
const int64_t maxOffset_dropSample = 1000;
avg = MEDIANFILTER_Insert(&shortMedianFilter, age + (-age_expect));
@@ -1285,20 +1412,27 @@ static void snapcast_sync_task(void *pvParameters) {
initialSync = 0;
alarmValSub = 0;
// Notify the task in the task's notification value that we lost initial sync
xTaskNotify( i2STaskHandle,
initialSync,
eSetValueWithOverwrite);
portYIELD(); // i2s task probably needs to reinitialize before we can continue
continue;
}
size_t writtenBytes;
int err = i2s_write(I2S_NUM_0, p_payload, size, &writtenBytes, pdMS_TO_TICKS(100));
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2S write error");
}
if (writtenBytes != size) {
ESP_LOGE(TAG, "written too less %u %u", size, writtenBytes);
}
i2sDmaBufferCnt += 2;
// size_t writtenBytes;
// int err = i2s_write(I2S_NUM_0, p_payload, size, &writtenBytes, pdMS_TO_TICKS(100));
// if (err != ESP_OK) {
// ESP_LOGE(TAG, "I2S write error");
// }
//
// if (writtenBytes != size) {
// ESP_LOGE(TAG, "written too less %u %u", size, writtenBytes);
// }
//
// i2sDmaBufferCnt += 2;
// NOT 100% SURE ABOUT THE FOLLOWING CONTROL LOOP, PROBABLY BETTER WAYS TO DO IT, STILL TESTING
int samples = 1;
@@ -1310,36 +1444,29 @@ static void snapcast_sync_task(void *pvParameters) {
// and tracking the buffer's fill state by counting events. Adding
// or deleting samples will confuse the algorithm...
if (enableControlLoop == 1) {
if (avg < -maxOffset) {
if (avg < -maxOffset) { // we are early
dir = -1;
// //if (avg < (age_expect - maxOffset_dropSample) ) {
// if (avg < -maxOffset_dropSample) {
// //ageDiff = (int)(age_expect - avg);
// ageDiff = (int)avg;
// ageDiff = -(int)avg;
// samples = ageDiff / (sampleDuration_ns / 1000);
// if (samples > 4) {
// samples = 4;
// }
//
// // too young add samples
// size_t writtenBytes;
//
// int err = i2s_write(I2S_NUM_0, p_payload, samples * sampleSize, &writtenBytes, portMAX_DELAY);
// if (err != ESP_OK) {
// ESP_LOGE(TAG, "I2S write error");
// }
//
// ESP_LOGI(TAG, "insert %d samples", samples);
//
// // insert samples
// writtenBytes = rb_write(i2sRingBufferHandle, p_payload, samples * sampleSize, pdMS_TO_TICKS(12));
// }
}
else if ((avg >= -maxOffset) && (avg <= maxOffset)) {
dir = 0;
}
else if (avg > maxOffset) {
else if (avg > maxOffset) { // we are late
dir = 1;
// //if (avg > (age_expect + maxOffset_dropSample)) {
// if (avg > maxOffset_dropSample) {
// //ageDiff = (int)(avg - age_expect);
// ageDiff = (int)avg;
@@ -1348,16 +1475,31 @@ static void snapcast_sync_task(void *pvParameters) {
// samples = 4;
// }
//
// // drop samples
// p_payload += samples * sampleSize;
// size -= samples * sampleSize;
// if (size >= samples * sampleSize) {
// // drop samples
// p_payload += samples * sampleSize;
// size -= samples * sampleSize;
//
// ESP_LOGI(TAG, "drop %d samples", samples);
// ESP_LOGI(TAG, "drop %d samples", samples);
// }
// }
}
adjust_apll(dir);
}
writtenBytes = rb_write(i2sRingBufferHandle, p_payload, size, pdMS_TO_TICKS(12));
// ESP_LOGI(TAG, "wrote %d samples", writtenBytes);
size -= writtenBytes;
p_payload += writtenBytes;
// ESP_LOGI(TAG, "size %d", size);
if ((chnk != NULL) && (size == 0)) {
free(chnk->payload);
free(chnk);
chnk = NULL;
}
}
int64_t t;
@@ -1365,7 +1507,7 @@ static void snapcast_sync_task(void *pvParameters) {
ESP_LOGI(TAG, "%d: %lldus, %lldus %lldus", dir, age, avg, t);
// ESP_LOGW(TAG, "chunk %d %d", uxQueueMessagesWaiting(pcmChunkQueueHandle), uxQueueMessagesWaiting(timestampQueueHandle));
if (chnk != NULL) {
if ((chnk != NULL) && (size == 0)) {
free(chnk->payload);
free(chnk);
chnk = NULL;
@@ -1378,13 +1520,18 @@ static void snapcast_sync_task(void *pvParameters) {
// audio_element_abort_input_ringbuf(decoder);
reset_latency_buffer(); // ensure correct latencies, if there is no stream received from server.
// latency will be shorter if no wirechunks have to be serviced and decoded
// reset_latency_buffer(); // ensure correct latencies, if there is no stream received from server.
// // latency will be shorter if no wirechunks have to be serviced and decoded
dir = 0;
initialSync = 0;
alarmValSub = 0;
// Notify the task in the task's notification value that we got initial sync
xTaskNotify( i2STaskHandle,
initialSync,
eSetValueWithOverwrite);
}
}
}
@@ -2142,33 +2289,206 @@ int flac_decoder_write_cb(audio_element_handle_t el, char *buf, int len, TickTyp
#define CONFIG_SLAVE_I2S_LRCK_PIN 12
#define CONFIG_SLAVE_I2S_DATAOUT_PIN 5
void setup_dsp_i2s(uint32_t sample_rate, i2s_port_t i2sNum)
esp_err_t setup_dsp_i2s(uint32_t sample_rate, i2s_port_t i2sNum)
{
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,
.dma_buf_count = 5,//32 + 0,//34,
.dma_buf_len = 576,//16,//16,
.intr_alloc_flags = 1, //Default interrupt priority
.use_apll = true,
.fixed_mclk = 0,
.tx_desc_auto_clear = true // Auto clear tx descriptor on underflow
};
int chunkInFrames = chunkInBytes / (CHANNELS * (BITS_PER_SAMPLE / 8));
int __dmaBufCnt = 2; // should be a multiple of 2
int __dmaBufLen = chunkInFrames / __dmaBufCnt;
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
};
if (chunkInFrames % __dmaBufCnt) {
ESP_LOGE(TAG, "setup_dsp_i2s: Can't setup i2s with this configuation");
i2s_driver_install(i2sNum, &i2s_config0, 7, &i2s_event_queue);
i2s_stop(i2sNum);
// i2s_zero_dma_buffer(I2S_NUM_0);
i2s_set_pin(i2sNum, &pin_config0);
return -1;
}
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,
.dma_buf_count = __dmaBufCnt + 1,//5,//32 + 0,//34,
.dma_buf_len = __dmaBufLen,//288,//16,//16,
.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
};
i2s_driver_install(i2sNum, &i2s_config0, 7, &i2s_event_queue);
// i2s_stop(i2sNum);
// i2s_zero_dma_buffer(I2S_NUM_0);
i2s_set_pin(i2sNum, &pin_config0);
return 0;
}
/**
*
*/
void i2s_playback_task(void *args) {
size_t writtenBytes, writtenBytesAccumulated = 0;
const int chunkInFrames = chunkInBytes / (CHANNELS * (BITS_PER_SAMPLE / 8));
int size;
//char payload[chunkInFrames]; // TODO: size needs to be calculated depending on sample rate, channels, etc. Should match DMA dma_buf_len
char payload[chunkInBytes];
uint32_t notifiedValue = 0;
int err;
int bytesAvailable;
int readBytes = 0;
while(1) {
switch (notifiedValue) {
case 0: // no initial sync
{
// ESP_LOGI(TAG, "i2s_playback_task: initial sync %u", notifiedValue);
writtenBytesAccumulated = 0;
if (rb_reset(i2sRingBufferHandle) != ESP_OK) {
ESP_LOGE(TAG, "i2s_playback_task: couldn't reset i2sRingBufferHandle");
vTaskDelay(1);
continue;
}
i2s_zero_dma_buffer(I2S_NUM_0); // this writes any data still preset out on i2s and then sets all bytes of buffer to 0
// i2s_stop(I2S_NUM_0); // stop i2s playback
// ESP_LOGI(TAG, "i2s_playback_task: initialize done");
// Notify the task that we are finished initializing
xTaskNotify( syncTaskHandle,
writtenBytesAccumulated,
eSetValueWithOverwrite);
// now we wait for sync task to notify us of initial sync
xTaskNotifyWait( pdFALSE, // Don't clear bits on entry.
pdFALSE, // Don't clear bits on exit.
&notifiedValue, // Stores the notified value.
portMAX_DELAY // block indefinitely
);
if (notifiedValue == 1) {
// ESP_LOGI(TAG, "i2s_playback_task: initial sync %u", notifiedValue);
// i2s_start(I2S_NUM_0); // ensure it is running
}
else {
// ESP_LOGW(TAG, "i2s_playback_task: something went wrong on initial sync %u", notifiedValue);
notifiedValue = 0;
}
break;
}
case 1: // got initial sync
{
xTaskNotifyWait( pdFALSE, // Don't clear bits on entry.
pdFALSE, // Don't clear bits on exit.
&notifiedValue, // Stores the notified value.
0 // don't block
);
if (notifiedValue == 1) {
bytesAvailable = rb_bytes_filled(i2sRingBufferHandle);
if (bytesAvailable) {
// test if we have just part of a full chunk in buffer, because of frame dropping
if ((bytesAvailable % chunkInBytes) == 0) {
readBytes = chunkInBytes;
// ESP_LOGW(TAG, "i2s_playback_task: full chunk %d", readBytes);
}
else {
readBytes = bytesAvailable % chunkInBytes; // ring buffer is 2 chunks in size, so get the remainder of division by 2
// ESP_LOGW(TAG, "i2s_playback_task: fractional chunk %d", readBytes);
}
// to be safe, ceil length
if (readBytes > sizeof(payload)) {
readBytes = sizeof(payload);
ESP_LOGW(TAG, "i2s_playback_task: can't read more than %d Bytes from ringbuf", sizeof(payload));
}
size = rb_read(i2sRingBufferHandle, payload, readBytes, pdMS_TO_TICKS(24));
if (size > 0) {
if (size != readBytes) {
ESP_LOGW(TAG, "i2s_playback_task: rb_read timed out %d", size);
}
// this function will block until all previously data has been written to DMA buffers
err = i2s_write(I2S_NUM_0, payload, (size_t)size, &writtenBytes, pdMS_TO_TICKS(24));
if (err != ESP_OK) {
ESP_LOGE(TAG, "i2s_playback_task: I2S write error");
}
if (size != writtenBytes) {
ESP_LOGE(TAG, "i2s_playback_task: couldn't write all data");
}
// writtenBytesAccumulated += writtenBytes;
// if (writtenBytesAccumulated >= chunkInBytes) {
// ESP_LOGI(TAG, "i2s_playback_task: writtenBytesAccumulated: %u", writtenBytesAccumulated);
// Notify sync task in the task's notification value that we sent (fractional) chunk
xTaskNotify( syncTaskHandle,
writtenBytesAccumulated,
eSetValueWithOverwrite);
// writtenBytesAccumulated -= chunkInBytes;
// }
// else {
// // for dropping/inserting chunks to work properly we need this check
// bytesAvailable = rb_bytes_filled(i2sRingBufferHandle);
// if (bytesAvailable < sizeof(payload)) {
// xTaskNotify( syncTaskHandle,
// writtenBytesAccumulated,
// eSetValueWithOverwrite);
// }
// }
}
else {
ESP_LOGW(TAG, "i2s_playback_task: size <= 0 %d", size);
vTaskDelay(1);
} // ensure it is running
}
else {
ESP_LOGW(TAG, "i2s_playback_task: no data in ringbuf");
vTaskDelay(1);
}
}
else {
ESP_LOGE(TAG, "i2s_playback_task: need resync %d", notifiedValue);
notifiedValue = 0;
}
break;
}
default:
{
ESP_LOGE(TAG, "i2s_playback_task: undefined");
notifiedValue = 0;
break;
}
}
}
return;
}
/**
@@ -2221,7 +2541,10 @@ void app_main(void) {
// };
// audio_hal_codec_iface_config(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, &i2sCfg);
i2s_mclk_gpio_select(I2S_NUM_0, GPIO_NUM_0);
setup_dsp_i2s(48000, I2S_NUM_0);
ret = setup_dsp_i2s(48000, I2S_NUM_0);
if (ret < 0) {
return;
}
ESP_LOGI(TAG, "Create audio pipeline for decoding");
audio_pipeline_cfg_t flac_dec_pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();