setting client latency from Android app works now, reduce latency buffer size, only start flac decoder if necessary
This commit is contained in:
26
README.md
26
README.md
@@ -36,24 +36,24 @@ Components
|
||||
- audio-board : taken from ADF, stripped down to strictly necessary parts for usage with Lyrat v4.3
|
||||
- audio-hal : taken from ADF, stripped down to strictly necessary parts for usage with Lyrat v4.3
|
||||
- audio-sal : taken from ADF, stripped down to strictly necessary parts for usage with Lyrat v4.3
|
||||
- custom_board :
|
||||
- custom_board :
|
||||
- custom-driver : modified I2S driver from IDF v4.3 which supports preloading DMA buffers with valid data
|
||||
- dsp_processor : Audio Processor, low pass filters, effects, etc.
|
||||
- esp-dsp : Submodule to the ESP-ADF done by David Douard
|
||||
- esp-peripherals : taken from ADF, stripped down to strictly necessary parts for usage with Lyrat v4.3
|
||||
- flac : flac audio cider/decoder full submodule
|
||||
- libmedian: Median Filter implementation. Many thanks to @accabog https://github.com/accabog/MedianFilter
|
||||
- libbuffer : Generic buffer abstraction
|
||||
- lightsnapcast :
|
||||
- libbuffer : Generic buffer abstraction
|
||||
- lightsnapcast :
|
||||
* snapcast module, port of @bridadan scapcast packages decode library
|
||||
* player module, which is responsible for sync and low level I2S control
|
||||
- net_functions :
|
||||
- net_functions :
|
||||
- opus : Opus audio coder/decoder full submodule
|
||||
- ota_server :
|
||||
- protocol :
|
||||
- ota_server :
|
||||
- protocol :
|
||||
- rtprx : Alternative RTP audio client UDP low latency also opus based
|
||||
- websocket :
|
||||
- websocket_if :
|
||||
- websocket :
|
||||
- websocket_if :
|
||||
- wifi_interface : wifi provisoning and init code for wifi module and AP connection
|
||||
|
||||
The snapclient functionanlity are implemented in a task included in main - but
|
||||
@@ -70,19 +70,19 @@ Normally these packages contain messages in the following order:
|
||||
- SERVER_SETTING : volume, mute state, playback delay etc
|
||||
- CODEC_HEADER : Setup client audio codec (FLAC, OPUS, OGG or PCM) bitrate, n
|
||||
channels and bits per sample
|
||||
- WIRE_CHUNK : Coded audio data, also I calculate chunk duration here after
|
||||
- WIRE_CHUNK : Coded audio data, also I calculate chunk duration here after
|
||||
decoding is done using received CODEC_HEADER parameters
|
||||
- TIME : Ping pong time keeping packages to keep track of time diff from server
|
||||
to client
|
||||
|
||||
Each WIRE_CHUNK of audio data comes with a timestamp in server time and clients
|
||||
can use information from TIME and SERVER_SETTING messages to determine when playback
|
||||
Each WIRE_CHUNK of audio data comes with a timestamp in server time and clients
|
||||
can use information from TIME and SERVER_SETTING messages to determine when playback
|
||||
has to be started. We handle this using a buffer with a length that compensate for for
|
||||
playback-delay, network jitter and DAC to speaker (determined through SERVER_SETTING).
|
||||
|
||||
In this implementation I have separated the sync task to a backend on the other
|
||||
end of a freeRTOS queue. Now the front end just needs to pass on the decoded audio
|
||||
data to the queue with the server timestamp and chunk size. The backend reads
|
||||
data to the queue with the server timestamp and chunk size. The backend reads
|
||||
timestamps and waits until the audio chunk has the correct playback-delay
|
||||
to be written to the DAC amplifer speaker pipeline. When the backend pipeline
|
||||
is in sync, any offset get rolled in by micro tuning the APLL on the ESP. No
|
||||
@@ -181,4 +181,4 @@ Then on every `git commit`, a few sanity/formatting checks will be performed.
|
||||
- [ok] Startup: do not start parsing on samples to codec before sample ring buffer hits requested buffer size.
|
||||
- [ok] Start from empty buffer
|
||||
- [ ] fill in missing component descriptions in Readme.md
|
||||
- [ ] DAC latency setting from android app
|
||||
- [ok] DAC latency setting from android app
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#define I2S_PORT I2S_NUM_0
|
||||
|
||||
#define LATENCY_MEDIAN_FILTER_LEN 99
|
||||
#define LATENCY_MEDIAN_FILTER_LEN 49 // 99
|
||||
|
||||
#define SHORT_BUFFER_LEN 29
|
||||
|
||||
|
||||
@@ -395,7 +395,8 @@ player_send_snapcast_setting (snapcastSetting_t *setting)
|
||||
if ((curSet.bits != setting->bits) || (curSet.buf_ms != setting->buf_ms)
|
||||
|| (curSet.ch != setting->ch) || (curSet.chkDur_ms != setting->chkDur_ms)
|
||||
|| (curSet.codec != setting->codec) || (curSet.muted != setting->muted)
|
||||
|| (curSet.sr != setting->sr) || (curSet.volume != setting->volume))
|
||||
|| (curSet.sr != setting->sr) || (curSet.volume != setting->volume)
|
||||
|| (curSet.cDacLat_ms != setting->cDacLat_ms))
|
||||
{
|
||||
// check if it is only volume / mute related setting, which is handled by
|
||||
// http_get_task()
|
||||
@@ -405,8 +406,8 @@ player_send_snapcast_setting (snapcastSetting_t *setting)
|
||||
&& (curSet.buf_ms == setting->buf_ms)
|
||||
&& (curSet.ch == setting->ch)
|
||||
&& (curSet.chkDur_ms == setting->chkDur_ms)
|
||||
&& (curSet.codec == setting->codec)
|
||||
&& (curSet.sr == setting->sr)))
|
||||
&& (curSet.codec == setting->codec) && (curSet.sr == setting->sr)
|
||||
&& (curSet.cDacLat_ms == setting->cDacLat_ms)))
|
||||
{
|
||||
// no notify needed, only set changed parameters
|
||||
ret = player_set_snapcast_settings (setting);
|
||||
@@ -1238,9 +1239,9 @@ player_task (void *pvParameters)
|
||||
|
||||
ESP_LOGI (TAG,
|
||||
"snapserver config changed, buffer %dms, chunk %dms, "
|
||||
"sample rate %d, ch %d, bits %d mute %d",
|
||||
"sample rate %d, ch %d, bits %d mute %d latency %d",
|
||||
scSet.buf_ms, scSet.chkDur_ms, scSet.sr, scSet.ch,
|
||||
scSet.bits, scSet.muted);
|
||||
scSet.bits, scSet.muted, scSet.cDacLat_ms);
|
||||
|
||||
gotSnapserverConfig = true;
|
||||
}
|
||||
|
||||
101
main/main.c
101
main/main.c
@@ -65,6 +65,7 @@ static void error_callback (const FLAC__StreamDecoder *decoder,
|
||||
|
||||
//#include "ma120.h"
|
||||
|
||||
static FLAC__StreamDecoder *flacDecoder = NULL;
|
||||
static QueueHandle_t flacReadQHdl = NULL;
|
||||
static QueueHandle_t flacWriteQHdl = NULL;
|
||||
|
||||
@@ -76,9 +77,12 @@ const char *VERSION_STRING = "0.0.2";
|
||||
#define OTA_TASK_PRIORITY 6
|
||||
#define OTA_TASK_CORE_ID tskNO_AFFINITY
|
||||
|
||||
xTaskHandle t_ota_task;
|
||||
xTaskHandle t_http_get_task;
|
||||
xTaskHandle t_flac_decoder_task;
|
||||
#define FLAC_TASK_PRIORITY HTTP_TASK_PRIORITY + 1
|
||||
#define FLAC_TASK_CORE_ID 1 // tskNO_AFFINITY
|
||||
|
||||
xTaskHandle t_ota_task = NULL;
|
||||
xTaskHandle t_http_get_task = NULL;
|
||||
xTaskHandle t_flac_decoder_task = NULL;
|
||||
|
||||
struct timeval tdif, tavg;
|
||||
audio_board_handle_t board_handle = NULL;
|
||||
@@ -271,7 +275,6 @@ static void
|
||||
flac_decoder_task (void *pvParameters)
|
||||
{
|
||||
FLAC__bool ok = true;
|
||||
FLAC__StreamDecoder *flacDecoder = NULL;
|
||||
FLAC__StreamDecoderInitStatus init_status;
|
||||
snapcastSetting_t *scSet = (snapcastSetting_t *)pvParameters;
|
||||
|
||||
@@ -381,11 +384,6 @@ http_get_task (void *pvParameters)
|
||||
mdns_init ();
|
||||
#endif
|
||||
|
||||
// TODO: only create this task, if we need flac
|
||||
// also add an ogg variant
|
||||
xTaskCreatePinnedToCore (&flac_decoder_task, "flac_decoder_task", 4 * 4096,
|
||||
&scSet, 6, &t_flac_decoder_task, 1);
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (reset_latency_buffer () < 0)
|
||||
@@ -406,6 +404,31 @@ http_get_task (void *pvParameters)
|
||||
opusDecoder = NULL;
|
||||
}
|
||||
|
||||
if (t_flac_decoder_task != NULL)
|
||||
{
|
||||
vTaskDelete (t_flac_decoder_task);
|
||||
t_flac_decoder_task = NULL;
|
||||
}
|
||||
|
||||
if (flacDecoder != NULL)
|
||||
{
|
||||
FLAC__stream_decoder_finish (flacDecoder);
|
||||
FLAC__stream_decoder_delete (flacDecoder);
|
||||
flacDecoder = NULL;
|
||||
}
|
||||
|
||||
if (flacWriteQHdl != NULL)
|
||||
{
|
||||
vQueueDelete (flacWriteQHdl);
|
||||
flacWriteQHdl = NULL;
|
||||
}
|
||||
|
||||
if (flacReadQHdl != NULL)
|
||||
{
|
||||
vQueueDelete (flacReadQHdl);
|
||||
flacReadQHdl = NULL;
|
||||
}
|
||||
|
||||
#if SNAPCAST_SERVER_USE_MDNS
|
||||
// Find snapcast server
|
||||
// Connect to first snapcast server found
|
||||
@@ -675,6 +698,37 @@ http_get_task (void *pvParameters)
|
||||
size = codec_header_message.size;
|
||||
start = codec_header_message.payload;
|
||||
|
||||
if (opusDecoder != NULL)
|
||||
{
|
||||
opus_decoder_destroy (opusDecoder);
|
||||
opusDecoder = NULL;
|
||||
}
|
||||
|
||||
if (t_flac_decoder_task != NULL)
|
||||
{
|
||||
vTaskDelete (t_flac_decoder_task);
|
||||
t_flac_decoder_task = NULL;
|
||||
}
|
||||
|
||||
if (flacDecoder != NULL)
|
||||
{
|
||||
FLAC__stream_decoder_finish (flacDecoder);
|
||||
FLAC__stream_decoder_delete (flacDecoder);
|
||||
flacDecoder = NULL;
|
||||
}
|
||||
|
||||
if (flacWriteQHdl != NULL)
|
||||
{
|
||||
vQueueDelete (flacWriteQHdl);
|
||||
flacWriteQHdl = NULL;
|
||||
}
|
||||
|
||||
if (flacReadQHdl != NULL)
|
||||
{
|
||||
vQueueDelete (flacReadQHdl);
|
||||
flacReadQHdl = NULL;
|
||||
}
|
||||
|
||||
// ESP_LOGI(TAG, "Received codec header message with size
|
||||
// %d", codec_header_message.size);
|
||||
|
||||
@@ -702,11 +756,6 @@ http_get_task (void *pvParameters)
|
||||
}
|
||||
|
||||
int error = 0;
|
||||
if (opusDecoder != NULL)
|
||||
{
|
||||
opus_decoder_destroy (opusDecoder);
|
||||
opusDecoder = NULL;
|
||||
}
|
||||
opusDecoder
|
||||
= opus_decoder_create (rate, channels, &error);
|
||||
if (error != 0)
|
||||
@@ -732,6 +781,14 @@ http_get_task (void *pvParameters)
|
||||
{
|
||||
codec = FLAC;
|
||||
|
||||
if (t_flac_decoder_task == NULL)
|
||||
{
|
||||
xTaskCreatePinnedToCore (
|
||||
&flac_decoder_task, "flac_decoder_task",
|
||||
4 * 4096, &scSet, FLAC_TASK_PRIORITY,
|
||||
&t_flac_decoder_task, FLAC_TASK_CORE_ID);
|
||||
}
|
||||
|
||||
// check if audio buffer was previously allocated by some
|
||||
// other codec this would happen if codec is changed
|
||||
// while client was running
|
||||
@@ -751,6 +808,12 @@ http_get_task (void *pvParameters)
|
||||
flacData.inData = codec_header_message.payload;
|
||||
pFlacData = &flacData;
|
||||
|
||||
// wait for task creation done
|
||||
while (flacReadQHdl == NULL)
|
||||
{
|
||||
vTaskDelay (10);
|
||||
}
|
||||
|
||||
// send data to flac decoder
|
||||
xQueueSend (flacReadQHdl, &pFlacData, portMAX_DELAY);
|
||||
// and wait until it is done
|
||||
@@ -908,7 +971,7 @@ http_get_task (void *pvParameters)
|
||||
audio = (int16_t *)realloc (
|
||||
audio,
|
||||
pcm_size * scSet.ch * (scSet.bits / 8));
|
||||
// audio = (int16_t
|
||||
// audio = (int16_t,
|
||||
// *)heap_caps_realloc(
|
||||
// (int32_t
|
||||
// *)audio, frameSize *
|
||||
@@ -982,11 +1045,17 @@ http_get_task (void *pvParameters)
|
||||
flacData.inData = wire_chunk_message.payload;
|
||||
pFlacData = &flacData;
|
||||
|
||||
// startTime =
|
||||
// esp_timer_get_time ();
|
||||
// send data to flac decoder
|
||||
xQueueSend (flacReadQHdl, &pFlacData, portMAX_DELAY);
|
||||
// and wait until it is done
|
||||
xQueueReceive (flacWriteQHdl, &pFlacData,
|
||||
portMAX_DELAY);
|
||||
// endTime =
|
||||
// esp_timer_get_time ();
|
||||
// ESP_LOGW(TAG,
|
||||
//"%lld", endTime - startTime);
|
||||
|
||||
wire_chunk_message_t pcm_chunk_message;
|
||||
|
||||
@@ -1267,7 +1336,7 @@ http_get_task (void *pvParameters)
|
||||
// Do a initial time sync with the server at boot
|
||||
// we need to fill diffBuff fast so we get a good
|
||||
// estimate of latency
|
||||
timeout = 50000;
|
||||
timeout = 100000;
|
||||
}
|
||||
|
||||
esp_timer_start_once (timeSyncMessageTimer, timeout);
|
||||
|
||||
@@ -546,7 +546,7 @@ CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
|
||||
# CONFIG_ESP32_WIFI_RX_IRAM_OPT is not set
|
||||
CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y
|
||||
# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set
|
||||
CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y
|
||||
# CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE is not set
|
||||
# end of Wi-Fi
|
||||
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user