- add ringbuffer and dedicated i2s task
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "components/opus/opus"]
|
||||
path = components/opus/opus
|
||||
url = https://github.com/xiph/opus
|
||||
20
components/opus/CMakeLists.txt
Normal file
20
components/opus/CMakeLists.txt
Normal 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")
|
||||
|
||||
|
||||
12
components/opus/component.mk
Normal file
12
components/opus/component.mk
Normal 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
208
components/opus/config.h
Normal 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
1
components/opus/opus
Submodule
Submodule components/opus/opus added at 8aa7767207
629
main/main.c
629
main/main.c
@@ -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.
|
||||
¬ifiedValue, // 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.
|
||||
¬ifiedValue, // 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.
|
||||
// ¬ifiedValue, // 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.
|
||||
¬ifiedValue, // 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.
|
||||
¬ifiedValue, // 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.
|
||||
¬ifiedValue, // 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.
|
||||
¬ifiedValue, // 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();
|
||||
|
||||
Reference in New Issue
Block a user