Skip to content

Commit 688c5b5

Browse files
committed
Synchronization start logic
1 parent 000aca4 commit 688c5b5

File tree

9 files changed

+151
-112
lines changed

9 files changed

+151
-112
lines changed

README.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- Auto connect to snapcast server on network
1111
- The functionality has been tested on an ESP32
1212
- Memory efficient implementation, so that PSRAM is not needed
13-
- Use any output device suppored by the [Audio Tools](https://github.com/pschatzmann/arduino-audio-tools)
13+
- Use any output device or DSP chain suppored by the [Audio Tools](https://github.com/pschatzmann/arduino-audio-tools)
1414

1515
### Description
1616

@@ -64,10 +64,9 @@ void loop() {
6464
### Dependencies
6565
6666
- [Audio Tools](https://github.com/pschatzmann/arduino-audio-tools)
67-
68-
- [LibOpus](https://github.com/pschatzmann/arduino-libopus)
69-
- [LibFLAC.h](https://github.com/pschatzmann/arduino-libflac)
70-
- [CodecVorbis](https://github.com/pschatzmann/arduino-libvorbis-idec)
67+
- [LibOpus](https://github.com/pschatzmann/arduino-libopus) (optional)
68+
- [LibFLAC.h](https://github.com/pschatzmann/arduino-libflac) (optinal)
69+
- [CodecVorbis](https://github.com/pschatzmann/arduino-libvorbis-idec) (optional)
7170
7271
7372
### Configuration

docs/html/SnapProcessor.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ class SnapProcessor {
101101
uint32_t client_state_muted = 0;
102102
struct timeval now, last_time_sync;
103103
uint8_t timestamp_size[12];
104-
float time_diff = 0.0;
105104
int id_counter = 0;
106105
IPAddress server_ip;
107106
int server_port = CONFIG_SNAPCAST_SERVER_PORT;
@@ -463,7 +462,7 @@ class SnapProcessor {
463462
retbuf[9] = (uavg & 0x000000ff);
464463
// ws_server_send_bin_client(0,(char*)retbuf, 10);
465464

466-
time_diff = time_message.latency.usec / 1000 +
465+
float time_diff = time_message.latency.usec / 1000 +
467466
base_message.received.usec / 1000 -
468467
base_message.sent.usec / 1000;
469468
time_diff = (time_diff > 1000) ? time_diff - 1000 : time_diff;

examples/SnapClientAudioKit/SnapClientAudioKit.ino

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
AudioKitStream out;
1616
//FLACDecoder flac;
1717
//VorbisDecoder vorbis;
18-
//PCMDecoder pcm;
18+
//WAVDecoder pcm;
1919
OpusAudioDecoder opus;
2020
SnapClient client(out, opus);
2121

src/SnapClient.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class SnapClient {
143143
const int daylightOffset_sec = 1 * 60 * 60;
144144
for (int retry = 0; retry < 5; retry++) {
145145
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
146-
if(!printLocalTime()){
146+
if(!SnapTime().instance().printLocalTime()){
147147
continue;
148148
}
149149
checkHeap();

src/SnapConfig.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#ifndef CONFIG_SNAPCLIENT_SNTP_ENABLE
1414
# define CONFIG_SNAPCLIENT_SNTP_ENABLE false
1515
#endif
16+
#ifndef CONFIG_SNAPCLIENT_SETTIME_ALLOWD
17+
# define CONFIG_SNAPCLIENT_SETTIME_ALLOWD true
18+
#endif
1619
#ifndef CONFIG_SNAPCLIENT_USE_MDNS
1720
# define CONFIG_SNAPCLIENT_USE_MDNS true
1821
#endif
@@ -23,7 +26,7 @@
2326
# define CONFIG_CHECK_HEAP true
2427
#endif
2528

26-
// snapast parameters;
29+
// snapcast parameters;
2730
#ifndef CONFIG_SNAPCAST_SERVER_HOST
2831
# define CONFIG_SNAPCAST_SERVER_HOST "192.168.1.38"
2932
#endif
@@ -42,6 +45,10 @@
4245
#ifndef CONFIG_CLIENT_TIMEOUT_SEC
4346
# define CONFIG_CLIENT_TIMEOUT_SEC 5
4447
#endif
48+
#ifndef CONFIG_PLAYBACK_LAG_MS
49+
# define CONFIG_PLAYBACK_LAG_MS 200
50+
#endif
51+
4552

4653
// wifi
4754
#define CONFIG_WIFI_SSID "piratnet"

src/api/SnapCommon.h

+3-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
*/
44

55
#pragma once
6-
#include <sys/time.h>
6+
#include <iostream>
7+
#include <iomanip>
8+
#include <ctime>
79

810
enum codec_type { NO_CODEC, PCM, FLAC, OGG, OPUS, VORBIS };
911
static const char *TAG="COMMON";
@@ -38,12 +40,3 @@ inline void logHeap() {
3840
#endif
3941
}
4042

41-
inline bool printLocalTime() {
42-
tm timeinfo;
43-
if (!getLocalTime(&timeinfo)) {
44-
ESP_LOGE(TAG, "Failed to obtain time");
45-
return false;
46-
}
47-
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
48-
return true;
49-
}

src/api/SnapOutputSimple.h

+7-5
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ class SnapOutputSimple : public SnapOutput {
2323
size_t write(const uint8_t *data, size_t size) {
2424
ESP_LOGD(TAG, "%zu", size);
2525

26-
// if(!synchAtStart()){
27-
// return size;
28-
// }
26+
if(!synchAtStart()){
27+
return size;
28+
}
2929

3030
size_t result = audioWrite(data, size);
3131
if (measure_stream.isUpdate()) measure_stream.logResult();
@@ -49,18 +49,20 @@ class SnapOutputSimple : public SnapOutput {
4949
SnapAudioHeader header;
5050
bool is_sync_active = true;
5151

52+
/// start to play audio only in valid server time
5253
bool synchAtStart(){
5354
// start audio when first package in the future becomes valid
5455
if (is_sync_active){
5556
auto msg_time = snap_time.toMillis(header.sec,header.usec);
5657
auto server_time = snap_time.serverMillis();
5758
if (msg_time < server_time){
5859
// ignore the data and report it as processed
59-
ESP_LOGW(TAG, "audio data expired...");
60+
ESP_LOGW(TAG, "audio data expired: msg(%u) < time(%u)", msg_time, server_time);
6061
return false;
6162
} else {
6263
// wait for the audio to become valid
63-
delay(msg_time - server_time);
64+
int diff_ms = msg_time - server_time;
65+
delay(diff_ms + SnapTime::instance().getStartDelay());
6466
is_sync_active = false;
6567
}
6668
}

src/api/SnapProcessor.h

+35-34
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class SnapProcessor {
9999
uint32_t client_state_muted = 0;
100100
char *start = nullptr;
101101
int size = 0;
102-
struct timeval now,tdif;
102+
timeval now;
103103
uint64_t last_time_sync = 0;
104104
int id_counter = 0;
105105
IPAddress server_ip;
@@ -108,6 +108,7 @@ class SnapProcessor {
108108
bool output_start = true;
109109
bool http_task_start = true;
110110
bool header_received = false;
111+
bool is_time_set = false;
111112
SnapTime& snap_time = SnapTime::instance();
112113

113114
void processLoop(void *pvParameters = nullptr) {
@@ -129,11 +130,7 @@ class SnapProcessor {
129130
return true;
130131
}
131132

132-
int result = gettimeofday(&now, NULL);
133-
if (result) {
134-
ESP_LOGI(TAG, "Failed to gettimeofday");
135-
return false;
136-
}
133+
now = snap_time.time();
137134

138135
if (!writeHallo())
139136
return false;
@@ -210,7 +207,7 @@ class SnapProcessor {
210207

211208
bool connectClient() {
212209
ESP_LOGD(TAG, "start");
213-
//p_client->setTimeout(CONFIG_CLIENT_TIMEOUT_SEC);
210+
p_client->setTimeout(CONFIG_CLIENT_TIMEOUT_SEC);
214211
if (!p_client->connect(server_ip, server_port)) {
215212
ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
216213
delay(4000);
@@ -279,14 +276,11 @@ class SnapProcessor {
279276
size = p_client->readBytes(&send_receive_buffer[0], BASE_MESSAGE_SIZE);
280277
ESP_LOGD(TAG, "Bytes read: %d", size);
281278

282-
int result = gettimeofday(&now, NULL);
283-
if (result) {
284-
ESP_LOGW(TAG, "Failed to gettimeofday");
285-
return false;
286-
}
279+
now = snap_time.time();
287280

288-
result =
289-
base_message_deserialize(&base_message, &send_receive_buffer[0], size);
281+
int result = base_message_deserialize(&base_message,
282+
&send_receive_buffer[0],
283+
size);
290284
if (result) {
291285
ESP_LOGW(TAG, "Failed to read base message: %d", result);
292286
return false;
@@ -327,7 +321,7 @@ class SnapProcessor {
327321
size = codec_header_message.size;
328322
start = codec_header_message.payload;
329323
if (strcmp(codec_header_message.codec, "opus") == 0) {
330-
if (!processMessageCodecHeaderExt(OPUS))
324+
if (!processMessageCodecHeaderOpus(OPUS))
331325
return false;
332326
} else if (strcmp(codec_header_message.codec, "flac") == 0) {
333327
if (!processMessageCodecHeaderExt(FLAC))
@@ -336,7 +330,7 @@ class SnapProcessor {
336330
if (!processMessageCodecHeaderExt(VORBIS))
337331
return false;
338332
} else if (strcmp(codec_header_message.codec, "pcm") == 0) {
339-
if (!processMessageCodecHeaderPCM())
333+
if (!processMessageCodecHeaderExt(PCM))
340334
return false;
341335
} else {
342336
ESP_LOGI(TAG, "Codec : %s not supported", codec_header_message.codec);
@@ -350,7 +344,7 @@ class SnapProcessor {
350344
return true;
351345
}
352346

353-
bool processMessageCodecHeaderExt(codec_type codecType) {
347+
bool processMessageCodecHeaderOpus(codec_type codecType) {
354348
ESP_LOGD(TAG, "start");
355349
uint32_t rate;
356350
memcpy(&rate, start + 4, sizeof(rate));
@@ -363,9 +357,9 @@ class SnapProcessor {
363357
return true;
364358
}
365359

366-
bool processMessageCodecHeaderPCM() {
360+
bool processMessageCodecHeaderExt(codec_type codecType) {
367361
ESP_LOGD(TAG, "start");
368-
codec_from_server = PCM;
362+
codec_from_server = codecType;
369363
return true;
370364
}
371365

@@ -428,6 +422,9 @@ class SnapProcessor {
428422
ESP_LOGI(TAG, "Mute: %d", server_settings_message.muted);
429423
ESP_LOGI(TAG, "Setting volume: %d", server_settings_message.volume);
430424

425+
// define the start delay
426+
snap_time.setDelay(server_settings_message.buffer_ms);
427+
431428
// set volume
432429
if (header_received){
433430
setMute(server_settings_message.muted);
@@ -447,29 +444,34 @@ class SnapProcessor {
447444
ESP_LOGI(TAG, "Failed to deserialize time message");
448445
return false;
449446
}
450-
// Calculate TClienctx.tdif : Trx-Tsend-Tnetdelay/2
447+
448+
// // Calculate TClienctx.tdif : Trx-Tsend-Tnetdelay/2
451449
struct timeval ttx, trx;
452450
ttx.tv_sec = base_message.sent.sec;
453451
ttx.tv_usec = base_message.sent.usec;
454452
trx.tv_sec = base_message.received.sec;
455453
trx.tv_usec = base_message.received.usec;
456-
snap_time.setTime(trx, ttx);
457-
458-
timersub(&trx, &ttx, &tdif);
459-
uint32_t usec = tdif.tv_usec;
454+
455+
snap_time.updateServerTime(trx);
456+
if (!is_time_set){
457+
// set time from server
458+
snap_time.setTime(trx);
459+
last_time_sync = 0;
460+
is_time_set = true;
461+
} else {
462+
int64_t time_diff = snap_time.timeDifferenceMs(trx, ttx);
463+
int time_diff_int = time_diff;
464+
assert(time_diff_int==time_diff);
465+
ESP_LOGI(TAG, "Time Difference to Server: %ld ms", time_diff);
466+
snap_time.setTimeDifferenceClientServerMs(time_diff);
467+
}
460468

461-
float time_diff = time_message.latency.usec / 1000 +
462-
base_message.received.usec / 1000 -
463-
base_message.sent.usec / 1000;
464-
time_diff = (time_diff > 1000) ? time_diff - 1000 : time_diff;
465-
ESP_LOGI(TAG, "TM loopback latency: %03.1f ms", time_diff);
466-
snap_time.addLatency(time_diff);
467469
return true;
468470
}
469471

470472
bool writeTimedMessage() {
471473
ESP_LOGD(TAG, "start");
472-
int time_ms = millis() - last_time_sync;
474+
uint32_t time_ms = millis() - last_time_sync;
473475
if (time_ms >= 1000) {
474476
last_time_sync = millis();
475477
if (!writeMessage()) {
@@ -481,9 +483,8 @@ class SnapProcessor {
481483

482484
bool writeMessage() {
483485
ESP_LOGD(TAG, "start");
484-
if (gettimeofday(&now, NULL)!=0){
485-
ESP_LOGE(TAG, "gettimeofday");
486-
}
486+
487+
now = snap_time.time();
487488

488489
base_message.type = SNAPCAST_MESSAGE_TIME;
489490
base_message.id = id_counter++;

0 commit comments

Comments
 (0)