setting client latency from Android app works now, reduce latency buffer size, only start flac decoder if necessary

This commit is contained in:
Carlos
2021-09-09 21:50:45 +02:00
Unverified
parent 14b3b41c1d
commit b65b4c24c7
5 changed files with 106 additions and 36 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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
#