Browse Source

Merge remote-tracking branch 'origin/master' into master-cmake

Conflicts:
	components/raop/raop.c
	components/raop/rtp.c
Sebastien 5 năm trước cách đây
mục cha
commit
804c67ef9a

+ 2 - 42
CMakeLists.txt

@@ -1,51 +1,11 @@
 cmake_minimum_required(VERSION 3.5)
 set(EXTRA_COMPONENT_DIRS components/platform_console/app_recovery components/platform_console/app_squeezelite  )
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
-message(WARNING  "Building RECOVERY")
 
+message(STATUS  "Building RECOVERY")
 project(recovery)
 set_property(TARGET recovery.elf PROPERTY RECOVERY_BUILD 0 ) 
 set_property(TARGET recovery.elf PROPERTY RECOVERY_PREFIX app_recovery )
 
-#
-#message(WARNING  "Building SQUEEZELITE")
-#set(project_name squeezelite)
-#__project(${project_name} C CXX ASM)
-#
-set(squeezelite_project_elf squeezelite.elf)
-#
-#    # Create a dummy file to work around CMake requirement of having a source
-#    # file while adding an executable
-set(squeezelite_project_elf_src ${CMAKE_BINARY_DIR}/squeezelite_project_elf_src.c)
-add_custom_command(OUTPUT ${squeezelite_project_elf_src}
-	COMMAND ${CMAKE_COMMAND} -E touch ${squeezelite_project_elf_src}
-    VERBATIM)
 
-
-    add_custom_command(OUTPUT "${build_dir}/.squeezelite_bin_timestamp"
-	    COMMAND echo ${ESPTOOLPY} elf2image ${ESPTOOLPY_FLASH_OPTIONS} ${esptool_elf2image_args}
-        COMMAND ${ESPTOOLPY} elf2image ${ESPTOOLPY_FLASH_OPTIONS} ${esptool_elf2image_args}
-            -o "${build_dir}/squeezelite.bin" "squeezelite.elf"
-        COMMAND ${CMAKE_COMMAND} -E echo "Generated ${build_dir}/squeezelite.bin"
-        COMMAND ${CMAKE_COMMAND} -E md5sum "${build_dir}/squeezelite.bin" > "${build_dir}/.squeezelite_bin_timestamp"
-        VERBATIM
-        WORKING_DIRECTORY ${build_dir}
-        COMMENT "Generating binary image from built executable"
-        )
-
-add_custom_target(_squeezelite_project_elf_src DEPENDS "${squeezelite_project_elf_src}" "${build_dir}/.squeezelite_bin_timestamp")
-add_executable(${squeezelite_project_elf} "${squeezelite_project_elf_src}")
-add_dependencies(${squeezelite_project_elf} _squeezelite_project_elf_src)
-set_property(TARGET ${squeezelite_project_elf} PROPERTY RECOVERY_BUILD 0 )
-set_property(TARGET ${squeezelite_project_elf} PROPERTY RECOVERY_PREFIX app_squeezelite)
-
-idf_build_get_property(bca BUILD_COMPONENT_ALIASES)            
-target_link_libraries(${squeezelite_project_elf} ${bca})
-set(squeezelite_mapfile "${CMAKE_BINARY_DIR}/squeezelite.map")
-target_link_libraries(${squeezelite_project_elf} "-Wl,--cref -Wl,--Map=${squeezelite_mapfile}")
-set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
-        ADDITIONAL_MAKE_CLEAN_FILES
-        "${squeezelite_mapfile}" "${squeezelite_project_elf_src}")
-
-partition_table_get_partition_info(otaapp_offset "--partition-type app --partition-subtype ota_0" "offset")
-esptool_py_flash_project_args(squeezelite ${otaapp_offset} ${build_dir}/squeezelite.bin FLASH_IN_PROJECT)
+include(squeezelite.cmake)

+ 23 - 5
README.md

@@ -6,7 +6,21 @@ Works with the SqueezeAMP see [here](https://forums.slimdevices.com/showthread.p
 Use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
 
 ### ESP32-A1S
-Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board or an external amplifier if you want direct speaker connection. 
+Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4000765857347.html?spm=2114.12010615.8148356.11.5d963cd0j669ns) or an external amplifier if you want direct speaker connection. 
+
+The board showed above has the following IO set
+- amplifier: GPIO21
+- key2: GPIO13, key3: GPIO19, key4: GPIO23, key5: GPIO18, key6: GPIO5 (to be confirmed width dipswitches)
+- key1: not sure, something with GPIO36
+- jack insertion: GPIO39 (inserted low)
+- LED: GPIO22 (active low)
+
+So a possible config would be
+- set_GPIO: 21=amp,22=green:0,39=jack:0
+- a button mapping: 
+```
+[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
+```
 
 ### ESP32-WROVER + I2S DAC
 Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
@@ -137,10 +151,14 @@ Where (all parameters are optionals except gpio)
  - "shifted": action to take when a button is pressed/released and shifted (see above/below)
  - "longshifted": action to take when a button is long-pressed/released and shifted (see above/below)
 
-Where \<action\> is either the name of another configuration to load or one amongst 
-				ACTRLS_NONE, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
-				ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
-				BCTRLS_PUSH, BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT
+Where \<action\> is either the name of another configuration to load (remap) or one amongst
+
+```
+ACTRLS_NONE, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
+ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
+BCTRLS_PUSH, BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
+KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH
+```
 				
 One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config
 

+ 0 - 6
components/platform_console/CMakeLists.txt

@@ -8,10 +8,4 @@ idf_component_register( SRCS
 						INCLUDE_DIRS .   
 						REQUIRES nvs_flash
 						PRIV_REQUIRES console  tools services   spi_flash app_update platform_config vfs pthread wifi-manager platform_config codecs newlib )
-
-
-#target_link_libraries(__idf_platform_console $<IF:${recovery_build}, ${build_dir}/esp-idf/app_recovery/libapp_recovery.a,${build_dir}/esp-idf/app_squeezelite/libapp_squeezelite.a >	)
-message($<TARGET_PROPERTY:RECOVERY_PREFIX>)
-
 target_link_libraries(__idf_platform_console ${build_dir}/esp-idf/$<TARGET_PROPERTY:RECOVERY_PREFIX>/lib$<TARGET_PROPERTY:RECOVERY_PREFIX>.a 	)
-#target_link_libraries(__idf_platform_console $<NOT ${recovery_build}:${build_dir}/esp-idf/app_squeezelite/libapp_squeezelite )

+ 6 - 0
components/raop/raop.c

@@ -436,6 +436,12 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		char *buf_pad, *p, *data_b64 = NULL, data[32];
 
 		LOG_INFO("[%p]: challenge %s", ctx, buf);
+		
+		// try to re-acquire IP address if we were missing it
+		if (S_ADDR(ctx->host) == INADDR_ANY) {
+			S_ADDR(ctx->host) = get_localhost(NULL);
+			LOG_INFO("[%p]: IP was missing, trying to get it %s", ctx, inet_ntoa(ctx->host));
+		}
 
 		// need to pad the base64 string as apple device don't
 		base64_pad(buf, &buf_pad);

+ 1 - 1
components/raop/raop_sink.c

@@ -163,7 +163,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
     	free(sink_name_buffer);
     }
 
-	LOG_INFO( "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
+	LOG_INFO( "mdns hostname for ip %s set to: [%s] with servicename %s", inet_ntoa(host), hostname, sink_name);
 
     // create RAOP instance, latency is set by controller
 	uint8_t mac[6];	

+ 807 - 806
components/raop/rtp.c

@@ -1,806 +1,807 @@
-
-/*
- * HairTunes - RAOP packet handler and slave-clocked replay engine
- * Copyright (c) James Laird 2011
- * All rights reserved.
- *
- * Modularisation: philippe_44@outlook.com, 2019
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <sys/types.h>
-#include <pthread.h>
-#include <math.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <stdint.h>
-#include <fcntl.h>
-#include <assert.h>
-
-#include "platform.h"
-#include "rtp.h"
-#include "raop_sink.h"
-#include "log_util.h"
-#include "util.h"
-
-#ifdef WIN32
-#include <openssl/aes.h>
-#include "alac_wrapper.h"
-#define MSG_DONTWAIT 0
-#else
-#include "esp_pthread.h"
-#include "esp_system.h"
-#include <mbedtls/version.h>
-#include <mbedtls/aes.h>
-#include "alac_wrapper.h"
-#endif
-
-#define NTP2MS(ntp) ((((ntp) >> 10) * 1000L) >> 22)
-#define MS2NTP(ms) (((((u64_t) (ms)) << 22) / 1000) << 10)
-#define NTP2TS(ntp, rate) ((((ntp) >> 16) * (rate)) >> 16)
-#define TS2NTP(ts, rate)  (((((u64_t) (ts)) << 16) / (rate)) << 16)
-#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
-#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
-
-
extern log_level 	raop_loglevel;
-
static log_level 	*loglevel = &raop_loglevel;
-
-//#define __RTP_STORE
-
-// default buffer size
-#define BUFFER_FRAMES 	( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
-#define MAX_PACKET       1408
-#define MIN_LATENCY		11025
-#define MAX_LATENCY   	( (120 * RAOP_SAMPLE_RATE * 2) / 100 )
-
-#define RTP_STACK_SIZE	(4*1024)
-
-#define RTP_SYNC	(0x01)
-#define NTP_SYNC	(0x02)
-
-#define RESEND_TO	200
-
-enum { DATA = 0, CONTROL, TIMING };
-
-static const u8_t silence_frame[MAX_PACKET] = { 0 };
-
-typedef u16_t seq_t;
-typedef struct audio_buffer_entry {   // decoded audio packets
-	int ready;
-	u32_t rtptime, last_resend;
-	s16_t *data;
-	int len;
-} abuf_t;
-
-typedef struct rtp_s {
-#ifdef __RTP_STORE
-	FILE *rtpIN, *rtpOUT;
-#endif
-	bool running;
-	unsigned char aesiv[16];
-#ifdef WIN32
-	AES_KEY aes;
-#else
-	mbedtls_aes_context aes;
-#endif
-	bool decrypt;
-	u8_t *decrypt_buf;
-	u32_t frame_size, frame_duration;
-	u32_t in_frames, out_frames;
-	struct in_addr host;
-	struct sockaddr_in rtp_host;
-	struct {
-		unsigned short rport, lport;
-		int sock;
-	} rtp_sockets[3]; 					 // data, control, timing
-	struct timing_s {
-		u64_t local, remote;
-	} timing;
-	struct {
-		u32_t 	rtp, time;
-		u8_t  	status;
-	} synchro;
-	struct {
-		u32_t time;
-		seq_t seqno;
-		u32_t rtptime;
-	} record;
-	int latency;			// rtp hold depth in samples
-	u32_t resent_req, resent_rec;	// total resent + recovered frames
-	u32_t silent_frames;	// total silence frames
-	u32_t discarded;
-	abuf_t audio_buffer[BUFFER_FRAMES];
-	seq_t ab_read, ab_write;
-	pthread_mutex_t ab_mutex;
-#ifdef WIN32
-	pthread_t thread;
-#else
-	TaskHandle_t thread, joiner;
-	StaticTask_t *xTaskBuffer;
-    StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
-#endif
-
-	struct alac_codec_s *alac_codec;
-	int flush_seqno;
-	bool playing;
-	raop_data_cb_t data_cb;
-	raop_cmd_cb_t cmd_cb;
-} rtp_t;
-
-
-#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
-static void 	buffer_alloc(abuf_t *audio_buffer, int size);
-static void 	buffer_release(abuf_t *audio_buffer);
-static void 	buffer_reset(abuf_t *audio_buffer);
-static void 	buffer_push_packet(rtp_t *ctx);
-static bool 	rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
-static bool 	rtp_request_timing(rtp_t *ctx);
-static void*	rtp_thread_func(void *arg);
-static int	  	seq_order(seq_t a, seq_t b);
-
-/*---------------------------------------------------------------------------*/
-static struct alac_codec_s* alac_init(int fmtp[32]) {
-	struct alac_codec_s *alac;
-	unsigned sample_rate;
-	unsigned char sample_size, channels;
-	struct {
-		uint32_t	frameLength;
-		uint8_t		compatibleVersion;
-		uint8_t		bitDepth;
-		uint8_t		pb;
-		uint8_t		mb;
-		uint8_t		kb;
-		uint8_t		numChannels;
-		uint16_t	maxRun;
-		uint32_t	maxFrameBytes;
-		uint32_t	avgBitRate;
-		uint32_t	sampleRate;
-	} config;
-
-	config.frameLength = htonl(fmtp[1]);
-	config.compatibleVersion = fmtp[2];
-	config.bitDepth = fmtp[3];
-	config.pb = fmtp[4];
-	config.mb = fmtp[5];
-	config.kb = fmtp[6];
-	config.numChannels = fmtp[7];
-	config.maxRun = htons(fmtp[8]);
-	config.maxFrameBytes = htonl(fmtp[9]);
-	config.avgBitRate = htonl(fmtp[10]);
-	config.sampleRate = htonl(fmtp[11]);
-
-	alac = alac_create_decoder(sizeof(config), (unsigned char*) &config, &sample_size, &sample_rate, &channels);
-	if (!alac) {
-		LOG_ERROR("cannot create alac codec", NULL);
-		return NULL;
-	}
-
-	return alac;
-}
-
-/*---------------------------------------------------------------------------*/
-rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, char *fmtpstr,
-								short unsigned pCtrlPort, short unsigned pTimingPort,
-								raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb)
-{
-	int i = 0;
-	char *arg;
-	int fmtp[12];
-	bool rc = true;
-	rtp_t *ctx = calloc(1, sizeof(rtp_t));
-	rtp_resp_t resp = { 0, 0, 0, NULL };
-
-	if (!ctx) return resp;
-	
-	ctx->host = host;
-	ctx->decrypt = false;
-	ctx->cmd_cb = cmd_cb;
-	ctx->data_cb = data_cb;
-	ctx->rtp_host.sin_family = AF_INET;
-	ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
-	pthread_mutex_init(&ctx->ab_mutex, 0);
-	ctx->flush_seqno = -1;
-	ctx->latency = latency;
-	ctx->ab_read = ctx->ab_write;
-
-#ifdef __RTP_STORE
-	ctx->rtpIN = fopen("airplay.rtpin", "wb");
-	ctx->rtpOUT = fopen("airplay.rtpout", "wb");
-#endif
-
-	ctx->rtp_sockets[CONTROL].rport = pCtrlPort;
-	ctx->rtp_sockets[TIMING].rport = pTimingPort;
-
-	if (aesiv && aeskey) {
-		memcpy(ctx->aesiv, aesiv, 16);
-#ifdef WIN32
-		AES_set_decrypt_key((unsigned char*) aeskey, 128, &ctx->aes);
-#else
-		memset(&ctx->aes, 0, sizeof(mbedtls_aes_context));
-		mbedtls_aes_setkey_dec(&ctx->aes, (unsigned char*) aeskey, 128);
-#endif
-		ctx->decrypt = true;
-		ctx->decrypt_buf = malloc(MAX_PACKET);
-	}
-
-	memset(fmtp, 0, sizeof(fmtp));
-	while ((arg = strsep(&fmtpstr, " \t")) != NULL) fmtp[i++] = atoi(arg);
-
-	ctx->frame_size = fmtp[1];
-	ctx->frame_duration = (ctx->frame_size * 1000) / RAOP_SAMPLE_RATE;
-
-	// alac decoder
-	ctx->alac_codec = alac_init(fmtp);
-	rc &= ctx->alac_codec != NULL;
-
-	buffer_alloc(ctx->audio_buffer, ctx->frame_size*4);
-
-	// create rtp ports
-	for (i = 0; i < 3; i++) {
-		ctx->rtp_sockets[i].sock = bind_socket(&ctx->rtp_sockets[i].lport, SOCK_DGRAM);
-		rc &= ctx->rtp_sockets[i].sock > 0;
-	}
-
-	// create http port and start listening
-	resp.cport = ctx->rtp_sockets[CONTROL].lport;
-	resp.tport = ctx->rtp_sockets[TIMING].lport;
-	resp.aport = ctx->rtp_sockets[DATA].lport;
-		
-	ctx->running = true;
-
-#ifdef WIN32
-	pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
-#else
-	ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
-	ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
-									 CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
-#endif
-	
-	// cleanup everything if we failed
-	if (!rc) {	
-		LOG_ERROR("[%p]: cannot start RTP", ctx);
-		rtp_end(ctx);
-		ctx = NULL;
-	}	
-	
-	resp.ctx = ctx;	
-	return resp;
-}
-
-/*---------------------------------------------------------------------------*/
-void rtp_end(rtp_t *ctx)
-{
-	int i;
-
-	if (!ctx) return;
-
-	if (ctx->running) {
-#if !defined WIN32		
-		ctx->joiner = xTaskGetCurrentTaskHandle();
-#endif
-		ctx->running = false;
-#ifdef WIN32
-		pthread_join(ctx->thread, NULL);
-#else
-		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
-		vTaskDelete(ctx->thread);
-		heap_caps_free(ctx->xTaskBuffer);
-#endif
-	}
-	
-	for (i = 0; i < 3; i++) closesocket(ctx->rtp_sockets[i].sock);
-
-	if (ctx->alac_codec) alac_delete_decoder(ctx->alac_codec);
-	if (ctx->decrypt_buf) free(ctx->decrypt_buf);
-	
-	pthread_mutex_destroy(&ctx->ab_mutex);
-	buffer_release(ctx->audio_buffer);
-	
-	free(ctx);
-
-#ifdef __RTP_STORE
-	fclose(ctx->rtpIN);
-	fclose(ctx->rtpOUT);
-#endif
-}
-
-/*---------------------------------------------------------------------------*/
-bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
-{
-	bool rc = true;
-	u32_t now = gettime_ms();
-
-	if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
-		rc = false;
-		LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
-	} else {
-		pthread_mutex_lock(&ctx->ab_mutex);
-		buffer_reset(ctx->audio_buffer);
-		ctx->playing = false;
-		ctx->flush_seqno = seqno;
-		if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
-	}
-
-	LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
-
-	return rc;
-}
-
-/*---------------------------------------------------------------------------*/
-void rtp_flush_release(rtp_t *ctx) {
-	pthread_mutex_unlock(&ctx->ab_mutex);
-}
-
-
-/*---------------------------------------------------------------------------*/
-void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
-	ctx->record.seqno = seqno;
-	ctx->record.rtptime = rtptime;
-	ctx->record.time = gettime_ms();
-
-	LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_alloc(abuf_t *audio_buffer, int size) {
-	int i;
-	for (i = 0; i < BUFFER_FRAMES; i++) {
-		audio_buffer[i].data = malloc(size);
-		audio_buffer[i].ready = 0;
-	}
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_release(abuf_t *audio_buffer) {
-	int i;
-	for (i = 0; i < BUFFER_FRAMES; i++) {
-		free(audio_buffer[i].data);
-	}
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_reset(abuf_t *audio_buffer) {
-	int i;
-	for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
-}
-
-/*---------------------------------------------------------------------------*/
-// the sequence numbers will wrap pretty often.
-// this returns true if the second arg is after the first
-static int seq_order(seq_t a, seq_t b) {
-	s16_t d = b - a;
-	return d > 0;
-}
-
-/*---------------------------------------------------------------------------*/
-static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
-	unsigned char iv[16];
-	int aeslen;
-	assert(len<=MAX_PACKET);
-
-	if (ctx->decrypt) {
-		aeslen = len & ~0xf;
-		memcpy(iv, ctx->aesiv, sizeof(iv));
-#ifdef WIN32
-		AES_cbc_encrypt((unsigned char*)buf, ctx->decrypt_buf, aeslen, &ctx->aes, iv, AES_DECRYPT);
-#else
-		mbedtls_aes_crypt_cbc(&ctx->aes, MBEDTLS_AES_DECRYPT, aeslen, iv, (unsigned char*) buf, ctx->decrypt_buf);
-#endif
-		memcpy(ctx->decrypt_buf+aeslen, buf+aeslen, len-aeslen);
-		alac_to_pcm(ctx->alac_codec, (unsigned char*) ctx->decrypt_buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
-	} else {
-		alac_to_pcm(ctx->alac_codec, (unsigned char*) buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
-	}	
-	
-	*outsize *= 4;
-}
-
-
-/*---------------------------------------------------------------------------*/
-static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
-	abuf_t *abuf = NULL;
-	u32_t playtime;
-
-	pthread_mutex_lock(&ctx->ab_mutex);
-
-	if (!ctx->playing) {
-		if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
-		   (ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
-			ctx->ab_write = seqno-1;
-			ctx->ab_read = seqno;
-			ctx->flush_seqno = -1;
-			ctx->playing = true;
-			ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
-			playtime = ctx->synchro.time + (((s32_t)(rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
-			ctx->cmd_cb(RAOP_PLAY, playtime);
-		} else {
-			pthread_mutex_unlock(&ctx->ab_mutex);
-			return;
-		}
-	}
-
-	if (seqno == (u16_t) (ctx->ab_write+1)) {
-		// expected packet
-		abuf = ctx->audio_buffer + BUFIDX(seqno);
-		ctx->ab_write = seqno;
-		LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
-
-	} else if (seq_order(ctx->ab_write, seqno)) {
-		seq_t i;
-		u32_t now;
-
-		// newer than expected
-		if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
-			// only get rtp latency-1 frames back (last one is seqno)
-			LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
-			ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
-		}
-
-		// need to request re-send and adjust timing of gaps
-		rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
-		for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
-			ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
-			ctx->audio_buffer[BUFIDX(i)].last_resend = now;
-		}
-
-		LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
-		abuf = ctx->audio_buffer + BUFIDX(seqno);
-		ctx->ab_write = seqno;
-	} else if (seq_order(ctx->ab_read, seqno + 1)) {
-		// recovered packet, not yet sent
-		abuf = ctx->audio_buffer + BUFIDX(seqno);
-		ctx->resent_rec++;
-		LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
-	} else {
-		// too late
-		LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
-	}
-
-	if (ctx->in_frames++ > 1000) {
-		LOG_INFO("[%p]: fill [level:%hu rec:%u] [W:%hu R:%hu]", ctx, ctx->ab_write - ctx->ab_read, ctx->resent_rec, ctx->ab_write, ctx->ab_read);
-		ctx->in_frames = 0;
-	}
-
-	if (abuf) {
-		alac_decode(ctx, abuf->data, data, len, &abuf->len);
-		abuf->ready = 1;
-		// this is the local rtptime when this frame is expected to play
-		abuf->rtptime = rtptime;
-		buffer_push_packet(ctx);
-
-#ifdef __RTP_STORE
-		fwrite(data, len, 1, ctx->rtpIN);
-		fwrite(abuf->data, abuf->len, 1, ctx->rtpOUT);
-#endif
-	}
-
-	pthread_mutex_unlock(&ctx->ab_mutex);
-}
-
-/*---------------------------------------------------------------------------*/
-// push as many frames as possible through callback
-static void buffer_push_packet(rtp_t *ctx) {
-	abuf_t *curframe = NULL;
-	u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
-	int i;
-
-	// not ready to play yet
-	if (!ctx->playing ||  ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
-
-	// maybe re-evaluate time in loop in case data callback blocks ...
-	now = gettime_ms();
-
-	// there is always at least one frame in the buffer
-	do {
-
-		curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
-		playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
-
-		if (now > playtime) {
-			LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read);
-			ctx->discarded++;
-			curframe->ready = 0;
-		} else if (playtime - now <= hold) {
-			if (curframe->ready) {
-				ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
-				curframe->ready = 0;
-			} else {
-				LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
-				ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
-				ctx->silent_frames++;
-			}
-		} else if (curframe->ready) {
-			ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
-			curframe->ready = 0;
-		} else {
-			break;
-		}
-
-		ctx->ab_read++;
-		ctx->out_frames++;
-
-	} while (seq_order(ctx->ab_read, ctx->ab_write));
-
-	if (ctx->out_frames > 1000) {
-		LOG_INFO("[%p]: drain [level:%hd head:%d ms] [W:%hu R:%hu] [req:%u sil:%u dis:%u]",
-				ctx, ctx->ab_write - ctx->ab_read, playtime - now, ctx->ab_write, ctx->ab_read,
-				ctx->resent_req, ctx->silent_frames, ctx->discarded);
-		ctx->out_frames = 0;
-	}
-
-	LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
-
-	// each missing packet will be requested up to (latency_frames / 16) times
-	for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
-		abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
-		if (!frame->ready && now - frame->last_resend > RESEND_TO) {
-			rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
-			frame->last_resend = now;
-		}
-	}
-
}
-
-
-/*---------------------------------------------------------------------------*/
-static void *rtp_thread_func(void *arg) {
-	fd_set fds;
-	int i, sock = -1;
-	int count = 0;
-	bool ntp_sent;
-	char *packet = malloc(MAX_PACKET);
-	rtp_t *ctx = (rtp_t*) arg;
-
-	for (i = 0; i < 3; i++) {
-		if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
-		// send synchro request 3 times
-		ntp_sent = rtp_request_timing(ctx);
-	}
-
-	while (ctx->running) {
-		ssize_t plen;
-		char type;
-		socklen_t rtp_client_len = sizeof(struct sockaddr_in);
-		int idx = 0;
-		char *pktp = packet;
-		struct timeval timeout = {0, 100*1000};
-
-		FD_ZERO(&fds);
-		for (i = 0; i < 3; i++)	{ FD_SET(ctx->rtp_sockets[i].sock, &fds); }
-
-		if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
-
-		for (i = 0; i < 3; i++)
-			if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i;
-
-		plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len);
-
-		if (!ntp_sent) {
-			LOG_WARN("[%p]: NTP request not send yet", ctx);
-			ntp_sent = rtp_request_timing(ctx);
-		}
-
-		if (plen <= 0) {
-			LOG_WARN("Nothing received on a readable socket %d", plen);
-			continue;
-		}
-		
-		assert(plen <= MAX_PACKET);
-
-		type = packet[1] & ~0x80;
-		pktp = packet;
-
-		switch (type) {
-			seq_t seqno;
-			unsigned rtptime;
-
-			// re-sent packet
-			case 0x56: {
-				pktp += 4;
-				plen -= 4;
-			}
-
-			// data packet
-			case 0x60: {
-				seqno = ntohs(*(u16_t*)(pktp+2));
-				rtptime = ntohl(*(u32_t*)(pktp+4));
-
-				// adjust pointer and length
-				pktp += 12;
-				plen -= 12;
-
-				LOG_SDEBUG("[%p]: seqno:%hu rtp:%u (type: %x, first: %u)", ctx, seqno, rtptime, type, packet[1] & 0x80);
-
-				// check if packet contains enough content to be reasonable
-				if (plen < 16) break;
-
-				if ((packet[1] & 0x80) && (type != 0x56)) {
-					LOG_INFO("[%p]: 1st audio packet received", ctx);
-				}
-
-				buffer_put_packet(ctx, seqno, rtptime, packet[1] & 0x80, pktp, plen);
-
-				break;
-			}
-
-			// sync packet
-			case 0x54: {
-				u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
-				u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
-				u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
-				u16_t flags = ntohs(*(u16_t*)(pktp+2));
-				u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
-
-				// something is wrong and if we are supposed to be NTP synced, better ask for re-sync
-				if (remote_gap > 10000) {
-					if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);
-					LOG_WARN("discarding remote timing information %u", remote_gap);
-					break;
-				}
-
-				pthread_mutex_lock(&ctx->ab_mutex);
-
-				// re-align timestamp and expected local playback time (and magic 11025 latency)
-				ctx->latency = rtp_now - rtp_now_latency;
-				if (flags == 7 || flags == 4) ctx->latency += 11025;
-				if (ctx->latency < MIN_LATENCY) ctx->latency = MIN_LATENCY;
-				else if (ctx->latency > MAX_LATENCY) ctx->latency = MAX_LATENCY;
-				ctx->synchro.rtp = rtp_now - ctx->latency;
-				ctx->synchro.time = ctx->timing.local + remote_gap;
-
-				// now we are synced on RTP frames
-				ctx->synchro.status |= RTP_SYNC;
-
-				// 1st sync packet received (signals a restart of playback)
-				if (packet[0] & 0x10) {
-					LOG_INFO("[%p]: 1st sync packet received", ctx);
-				}
-
-				pthread_mutex_unlock(&ctx->ab_mutex);
-
-				LOG_DEBUG("[%p]: sync packet latency:%d rtp_latency:%u rtp:%u remote ntp:%llx, local time:%u local rtp:%u (now:%u)",
-						  ctx, ctx->latency, rtp_now_latency, rtp_now, remote, ctx->synchro.time, ctx->synchro.rtp, gettime_ms());
-
-				if (!count--) {
-					rtp_request_timing(ctx);
-					count = 3;
-				}
-
-				if ((ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) ctx->cmd_cb(RAOP_TIMING);
-
-				break;
-			}
-
-			// NTP timing packet
-			case 0x53: {
-				u64_t expected;
-				u32_t reference   = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
-				u64_t remote 	  =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
-				u32_t roundtrip   = gettime_ms() - reference;
-
-				// better discard sync packets when roundtrip is suspicious and ask for another one
-				if (roundtrip > 100) {
-					rtp_request_timing(ctx);
-					LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
-					break;
-				}
-
-				/*
-				  The expected elapsed remote time should be exactly the same as
-				  elapsed local time between the two request, corrected by the
-				  drifting
-				*/
-				expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
-
-				ctx->timing.remote = remote;
-				ctx->timing.local = reference;
-
-				// now we are synced on NTP (mutex not needed)
-				ctx->synchro.status |= NTP_SYNC;
-
-				LOG_DEBUG("[%p]: Timing references local:%llu, remote:%llx (delta:%lld, sum:%lld, adjust:%lld, gaps:%d)",
-						  ctx, ctx->timing.local, ctx->timing.remote);
-
-				break;
-			}
-			
-			default: {
-				LOG_WARN("Unknown packet received %x", (int) type);
-				break;
-			}
-		}
-	}
-
-	free(packet);
-	LOG_INFO("[%p]: terminating", ctx);
-
-#ifndef WIN32
-	xTaskNotifyGive(ctx->joiner);
-	vTaskSuspend(NULL);
-#endif
-
-	return NULL;
-}
-
-/*---------------------------------------------------------------------------*/
-static bool rtp_request_timing(rtp_t *ctx) {
-	unsigned char req[32];
-	u32_t now = gettime_ms();
-	int i;
-	struct sockaddr_in host;
-
-	LOG_DEBUG("[%p]: timing request now:%u (port: %hu)", ctx, now, ctx->rtp_sockets[TIMING].rport);
-
-	req[0] = 0x80;
-	req[1] = 0x52|0x80;
-	*(u16_t*)(req+2) = htons(7);
-	*(u32_t*)(req+4) = htonl(0);  // dummy
-	for (i = 0; i < 16; i++) req[i+8] = 0;
-	*(u32_t*)(req+24) = 0;
-	*(u32_t*)(req+28) = htonl(now); // this is not a real NTP, but a 32 ms counter in the low part of the NTP
-
-	if (ctx->host.s_addr != INADDR_ANY) {
-		host.sin_family = AF_INET;
-		host.sin_addr =	ctx->host;
-	} else host = ctx->rtp_host;
-
-	// no address from sender, need to wait for 1st packet to be received
-	if (host.sin_addr.s_addr == INADDR_ANY) return false;
-
-	host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
-
-	if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &host, sizeof(host))) {
-		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
-	}
-
-	return true;
-}
-
-/*---------------------------------------------------------------------------*/
-static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
-	unsigned char req[8];    // *not* a standard RTCP NACK
-
-	// do not request silly ranges (happens in case of network large blackouts)
-	if (seq_order(last, first) || last - first > BUFFER_FRAMES / 2) return false;
-	
-	ctx->resent_req += last - first + 1;
-
-	LOG_DEBUG("resend request [W:%hu R:%hu first=%hu last=%hu]", ctx->ab_write, ctx->ab_read, first, last);
-
-	req[0] = 0x80;
-	req[1] = 0x55|0x80;  // Apple 'resend'
-	*(u16_t*)(req+2) = htons(1);  // our seqnum
-	*(u16_t*)(req+4) = htons(first);  // missed seqnum
-	*(u16_t*)(req+6) = htons(last-first+1);  // count
-
-	ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
-
-	if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
-		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
-	}
-
-	return true;
-}
-
+
+/*
+ * HairTunes - RAOP packet handler and slave-clocked replay engine
+ * Copyright (c) James Laird 2011
+ * All rights reserved.
+ *
+ * Modularisation: philippe_44@outlook.com, 2019
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <pthread.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <fcntl.h>
+
+#include "platform.h"
+#include "rtp.h"
+#include "raop_sink.h"
+#include "log_util.h"
+#include "util.h"
+
+#ifdef WIN32
+#include <openssl/aes.h>
+#include "alac_wrapper.h"
+#include "assert.h"
+#define MSG_DONTWAIT 0
+#else
+#include "esp_pthread.h"
+#include "esp_system.h"
+#include "esp_assert.h"
+#include <mbedtls/version.h>
+#include <mbedtls/aes.h>
+#include "alac_wrapper.h"
+#endif
+
+#define NTP2MS(ntp) ((((ntp) >> 10) * 1000L) >> 22)
+#define MS2NTP(ms) (((((u64_t) (ms)) << 22) / 1000) << 10)
+#define NTP2TS(ntp, rate) ((((ntp) >> 16) * (rate)) >> 16)
+#define TS2NTP(ts, rate)  (((((u64_t) (ts)) << 16) / (rate)) << 16)
+#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
+#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
+
+
extern log_level 	raop_loglevel;
+
static log_level 	*loglevel = &raop_loglevel;
+
+//#define __RTP_STORE
+
+// default buffer size
+#define BUFFER_FRAMES 	( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
+#define MAX_PACKET       1408
+#define MIN_LATENCY		11025
+#define MAX_LATENCY   	( (120 * RAOP_SAMPLE_RATE * 2) / 100 )
+
+#define RTP_STACK_SIZE	(4*1024)
+
+#define RTP_SYNC	(0x01)
+#define NTP_SYNC	(0x02)
+
+#define RESEND_TO	200
+
+enum { DATA = 0, CONTROL, TIMING };
+
+static const u8_t silence_frame[MAX_PACKET] = { 0 };
+
+typedef u16_t seq_t;
+typedef struct audio_buffer_entry {   // decoded audio packets
+	int ready;
+	u32_t rtptime, last_resend;
+	s16_t *data;
+	int len;
+} abuf_t;
+
+typedef struct rtp_s {
+#ifdef __RTP_STORE
+	FILE *rtpIN, *rtpOUT;
+#endif
+	bool running;
+	unsigned char aesiv[16];
+#ifdef WIN32
+	AES_KEY aes;
+#else
+	mbedtls_aes_context aes;
+#endif
+	bool decrypt;
+	u8_t *decrypt_buf;
+	u32_t frame_size, frame_duration;
+	u32_t in_frames, out_frames;
+	struct in_addr host;
+	struct sockaddr_in rtp_host;
+	struct {
+		unsigned short rport, lport;
+		int sock;
+	} rtp_sockets[3]; 					 // data, control, timing
+	struct timing_s {
+		u64_t local, remote;
+	} timing;
+	struct {
+		u32_t 	rtp, time;
+		u8_t  	status;
+	} synchro;
+	struct {
+		u32_t time;
+		seq_t seqno;
+		u32_t rtptime;
+	} record;
+	int latency;			// rtp hold depth in samples
+	u32_t resent_req, resent_rec;	// total resent + recovered frames
+	u32_t silent_frames;	// total silence frames
+	u32_t discarded;
+	abuf_t audio_buffer[BUFFER_FRAMES];
+	seq_t ab_read, ab_write;
+	pthread_mutex_t ab_mutex;
+#ifdef WIN32
+	pthread_t thread;
+#else
+	TaskHandle_t thread, joiner;
+	StaticTask_t *xTaskBuffer;
+    StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
+#endif
+
+	struct alac_codec_s *alac_codec;
+	int flush_seqno;
+	bool playing;
+	raop_data_cb_t data_cb;
+	raop_cmd_cb_t cmd_cb;
+} rtp_t;
+
+
+#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
+static void 	buffer_alloc(abuf_t *audio_buffer, int size);
+static void 	buffer_release(abuf_t *audio_buffer);
+static void 	buffer_reset(abuf_t *audio_buffer);
+static void 	buffer_push_packet(rtp_t *ctx);
+static bool 	rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
+static bool 	rtp_request_timing(rtp_t *ctx);
+static void*	rtp_thread_func(void *arg);
+static int	  	seq_order(seq_t a, seq_t b);
+
+/*---------------------------------------------------------------------------*/
+static struct alac_codec_s* alac_init(int fmtp[32]) {
+	struct alac_codec_s *alac;
+	unsigned sample_rate;
+	unsigned char sample_size, channels;
+	struct {
+		uint32_t	frameLength;
+		uint8_t		compatibleVersion;
+		uint8_t		bitDepth;
+		uint8_t		pb;
+		uint8_t		mb;
+		uint8_t		kb;
+		uint8_t		numChannels;
+		uint16_t	maxRun;
+		uint32_t	maxFrameBytes;
+		uint32_t	avgBitRate;
+		uint32_t	sampleRate;
+	} config;
+
+	config.frameLength = htonl(fmtp[1]);
+	config.compatibleVersion = fmtp[2];
+	config.bitDepth = fmtp[3];
+	config.pb = fmtp[4];
+	config.mb = fmtp[5];
+	config.kb = fmtp[6];
+	config.numChannels = fmtp[7];
+	config.maxRun = htons(fmtp[8]);
+	config.maxFrameBytes = htonl(fmtp[9]);
+	config.avgBitRate = htonl(fmtp[10]);
+	config.sampleRate = htonl(fmtp[11]);
+
+	alac = alac_create_decoder(sizeof(config), (unsigned char*) &config, &sample_size, &sample_rate, &channels);
+	if (!alac) {
+		LOG_ERROR("cannot create alac codec", NULL);
+		return NULL;
+	}
+
+	return alac;
+}
+
+/*---------------------------------------------------------------------------*/
+rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, char *fmtpstr,
+								short unsigned pCtrlPort, short unsigned pTimingPort,
+								raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb)
+{
+	int i = 0;
+	char *arg;
+	int fmtp[12];
+	bool rc = true;
+	rtp_t *ctx = calloc(1, sizeof(rtp_t));
+	rtp_resp_t resp = { 0, 0, 0, NULL };
+
+	if (!ctx) return resp;
+	
+	ctx->host = host;
+	ctx->decrypt = false;
+	ctx->cmd_cb = cmd_cb;
+	ctx->data_cb = data_cb;
+	ctx->rtp_host.sin_family = AF_INET;
+	ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
+	pthread_mutex_init(&ctx->ab_mutex, 0);
+	ctx->flush_seqno = -1;
+	ctx->latency = latency;
+	ctx->ab_read = ctx->ab_write;
+
+#ifdef __RTP_STORE
+	ctx->rtpIN = fopen("airplay.rtpin", "wb");
+	ctx->rtpOUT = fopen("airplay.rtpout", "wb");
+#endif
+
+	ctx->rtp_sockets[CONTROL].rport = pCtrlPort;
+	ctx->rtp_sockets[TIMING].rport = pTimingPort;
+
+	if (aesiv && aeskey) {
+		memcpy(ctx->aesiv, aesiv, 16);
+#ifdef WIN32
+		AES_set_decrypt_key((unsigned char*) aeskey, 128, &ctx->aes);
+#else
+		memset(&ctx->aes, 0, sizeof(mbedtls_aes_context));
+		mbedtls_aes_setkey_dec(&ctx->aes, (unsigned char*) aeskey, 128);
+#endif
+		ctx->decrypt = true;
+		ctx->decrypt_buf = malloc(MAX_PACKET);
+	}
+
+	memset(fmtp, 0, sizeof(fmtp));
+	while ((arg = strsep(&fmtpstr, " \t")) != NULL) fmtp[i++] = atoi(arg);
+
+	ctx->frame_size = fmtp[1];
+	ctx->frame_duration = (ctx->frame_size * 1000) / RAOP_SAMPLE_RATE;
+
+	// alac decoder
+	ctx->alac_codec = alac_init(fmtp);
+	rc &= ctx->alac_codec != NULL;
+
+	buffer_alloc(ctx->audio_buffer, ctx->frame_size*4);
+
+	// create rtp ports
+	for (i = 0; i < 3; i++) {
+		ctx->rtp_sockets[i].sock = bind_socket(&ctx->rtp_sockets[i].lport, SOCK_DGRAM);
+		rc &= ctx->rtp_sockets[i].sock > 0;
+	}
+
+	// create http port and start listening
+	resp.cport = ctx->rtp_sockets[CONTROL].lport;
+	resp.tport = ctx->rtp_sockets[TIMING].lport;
+	resp.aport = ctx->rtp_sockets[DATA].lport;
+		
+	ctx->running = true;
+
+#ifdef WIN32
+	pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
+#else
+	ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
+	ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
+									 CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
+#endif
+	
+	// cleanup everything if we failed
+	if (!rc) {	
+		LOG_ERROR("[%p]: cannot start RTP", ctx);
+		rtp_end(ctx);
+		ctx = NULL;
+	}	
+	
+	resp.ctx = ctx;	
+	return resp;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_end(rtp_t *ctx)
+{
+	int i;
+
+	if (!ctx) return;
+
+	if (ctx->running) {
+#if !defined WIN32		
+		ctx->joiner = xTaskGetCurrentTaskHandle();
+#endif
+		ctx->running = false;
+#ifdef WIN32
+		pthread_join(ctx->thread, NULL);
+#else
+		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+		vTaskDelete(ctx->thread);
+		heap_caps_free(ctx->xTaskBuffer);
+#endif
+	}
+	
+	for (i = 0; i < 3; i++) closesocket(ctx->rtp_sockets[i].sock);
+
+	if (ctx->alac_codec) alac_delete_decoder(ctx->alac_codec);
+	if (ctx->decrypt_buf) free(ctx->decrypt_buf);
+	
+	pthread_mutex_destroy(&ctx->ab_mutex);
+	buffer_release(ctx->audio_buffer);
+	
+	free(ctx);
+
+#ifdef __RTP_STORE
+	fclose(ctx->rtpIN);
+	fclose(ctx->rtpOUT);
+#endif
+}
+
+/*---------------------------------------------------------------------------*/
+bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
+{
+	bool rc = true;
+	u32_t now = gettime_ms();
+
+	if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
+		rc = false;
+		LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
+	} else {
+		pthread_mutex_lock(&ctx->ab_mutex);
+		buffer_reset(ctx->audio_buffer);
+		ctx->playing = false;
+		ctx->flush_seqno = seqno;
+		if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
+	}
+
+	LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
+
+	return rc;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_flush_release(rtp_t *ctx) {
+	pthread_mutex_unlock(&ctx->ab_mutex);
+}
+
+
+/*---------------------------------------------------------------------------*/
+void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
+	ctx->record.seqno = seqno;
+	ctx->record.rtptime = rtptime;
+	ctx->record.time = gettime_ms();
+
+	LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_alloc(abuf_t *audio_buffer, int size) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) {
+		audio_buffer[i].data = malloc(size);
+		audio_buffer[i].ready = 0;
+	}
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_release(abuf_t *audio_buffer) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) {
+		free(audio_buffer[i].data);
+	}
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_reset(abuf_t *audio_buffer) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+// the sequence numbers will wrap pretty often.
+// this returns true if the second arg is after the first
+static int seq_order(seq_t a, seq_t b) {
+	s16_t d = b - a;
+	return d > 0;
+}
+
+/*---------------------------------------------------------------------------*/
+static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
+	unsigned char iv[16];
+	int aeslen;
+	assert(len<=MAX_PACKET);
+
+	if (ctx->decrypt) {
+		aeslen = len & ~0xf;
+		memcpy(iv, ctx->aesiv, sizeof(iv));
+#ifdef WIN32
+		AES_cbc_encrypt((unsigned char*)buf, ctx->decrypt_buf, aeslen, &ctx->aes, iv, AES_DECRYPT);
+#else
+		mbedtls_aes_crypt_cbc(&ctx->aes, MBEDTLS_AES_DECRYPT, aeslen, iv, (unsigned char*) buf, ctx->decrypt_buf);
+#endif
+		memcpy(ctx->decrypt_buf+aeslen, buf+aeslen, len-aeslen);
+		alac_to_pcm(ctx->alac_codec, (unsigned char*) ctx->decrypt_buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
+	} else {
+		alac_to_pcm(ctx->alac_codec, (unsigned char*) buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
+	}	
+	
+	*outsize *= 4;
+}
+
+
+/*---------------------------------------------------------------------------*/
+static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
+	abuf_t *abuf = NULL;
+	u32_t playtime;
+
+	pthread_mutex_lock(&ctx->ab_mutex);
+
+	if (!ctx->playing) {
+		if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
+		   (ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
+			ctx->ab_write = seqno-1;
+			ctx->ab_read = seqno;
+			ctx->flush_seqno = -1;
+			ctx->playing = true;
+			ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
+			playtime = ctx->synchro.time + (((s32_t)(rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
+			ctx->cmd_cb(RAOP_PLAY, playtime);
+		} else {
+			pthread_mutex_unlock(&ctx->ab_mutex);
+			return;
+		}
+	}
+
+	if (seqno == (u16_t) (ctx->ab_write+1)) {
+		// expected packet
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->ab_write = seqno;
+		LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
+
+	} else if (seq_order(ctx->ab_write, seqno)) {
+		seq_t i;
+		u32_t now;
+
+		// newer than expected
+		if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
+			// only get rtp latency-1 frames back (last one is seqno)
+			LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
+			ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
+		}
+
+		// need to request re-send and adjust timing of gaps
+		rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
+		for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
+			ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
+			ctx->audio_buffer[BUFIDX(i)].last_resend = now;
+		}
+
+		LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->ab_write = seqno;
+	} else if (seq_order(ctx->ab_read, seqno + 1)) {
+		// recovered packet, not yet sent
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->resent_rec++;
+		LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+	} else {
+		// too late
+		LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+	}
+
+	if (ctx->in_frames++ > 1000) {
+		LOG_INFO("[%p]: fill [level:%hu rec:%u] [W:%hu R:%hu]", ctx, ctx->ab_write - ctx->ab_read, ctx->resent_rec, ctx->ab_write, ctx->ab_read);
+		ctx->in_frames = 0;
+	}
+
+	if (abuf) {
+		alac_decode(ctx, abuf->data, data, len, &abuf->len);
+		abuf->ready = 1;
+		// this is the local rtptime when this frame is expected to play
+		abuf->rtptime = rtptime;
+		buffer_push_packet(ctx);
+
+#ifdef __RTP_STORE
+		fwrite(data, len, 1, ctx->rtpIN);
+		fwrite(abuf->data, abuf->len, 1, ctx->rtpOUT);
+#endif
+	}
+
+	pthread_mutex_unlock(&ctx->ab_mutex);
+}
+
+/*---------------------------------------------------------------------------*/
+// push as many frames as possible through callback
+static void buffer_push_packet(rtp_t *ctx) {
+	abuf_t *curframe = NULL;
+	u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
+	int i;
+
+	// not ready to play yet
+	if (!ctx->playing ||  ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
+
+	// maybe re-evaluate time in loop in case data callback blocks ...
+	now = gettime_ms();
+
+	// there is always at least one frame in the buffer
+	do {
+
+		curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
+		playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
+
+		if (now > playtime) {
+			LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read);
+			ctx->discarded++;
+			curframe->ready = 0;
+		} else if (playtime - now <= hold) {
+			if (curframe->ready) {
+				ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
+				curframe->ready = 0;
+			} else {
+				LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+				ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
+				ctx->silent_frames++;
+			}
+		} else if (curframe->ready) {
+			ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
+			curframe->ready = 0;
+		} else {
+			break;
+		}
+
+		ctx->ab_read++;
+		ctx->out_frames++;
+
+	} while (seq_order(ctx->ab_read, ctx->ab_write));
+
+	if (ctx->out_frames > 1000) {
+		LOG_INFO("[%p]: drain [level:%hd head:%d ms] [W:%hu R:%hu] [req:%u sil:%u dis:%u]",
+				ctx, ctx->ab_write - ctx->ab_read, playtime - now, ctx->ab_write, ctx->ab_read,
+				ctx->resent_req, ctx->silent_frames, ctx->discarded);
+		ctx->out_frames = 0;
+	}
+
+	LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
+
+	// each missing packet will be requested up to (latency_frames / 16) times
+	for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
+		abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
+		if (!frame->ready && now - frame->last_resend > RESEND_TO) {
+			rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
+			frame->last_resend = now;
+		}
+	}
+
}
+
+
+/*---------------------------------------------------------------------------*/
+static void *rtp_thread_func(void *arg) {
+	fd_set fds;
+	int i, sock = -1;
+	int count = 0;
+	bool ntp_sent;
+	char *packet = malloc(MAX_PACKET);
+	rtp_t *ctx = (rtp_t*) arg;
+
+	for (i = 0; i < 3; i++) {
+		if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
+		// send synchro request 3 times
+		ntp_sent = rtp_request_timing(ctx);
+	}
+
+	while (ctx->running) {
+		ssize_t plen;
+		char type;
+		socklen_t rtp_client_len = sizeof(struct sockaddr_in);
+		int idx = 0;
+		char *pktp = packet;
+		struct timeval timeout = {0, 100*1000};
+
+		FD_ZERO(&fds);
+		for (i = 0; i < 3; i++)	{ FD_SET(ctx->rtp_sockets[i].sock, &fds); }
+
+		if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
+
+		for (i = 0; i < 3; i++)
+			if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i;
+
+		plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len);
+
+		if (!ntp_sent) {
+			LOG_WARN("[%p]: NTP request not send yet", ctx);
+			ntp_sent = rtp_request_timing(ctx);
+		}
+
+		if (plen <= 0) {
+			LOG_WARN("Nothing received on a readable socket %d", plen);
+			continue;
+		}
+		
+		assert(plen <= MAX_PACKET);
+
+		type = packet[1] & ~0x80;
+		pktp = packet;
+
+		switch (type) {
+			seq_t seqno;
+			unsigned rtptime;
+
+			// re-sent packet
+			case 0x56: {
+				pktp += 4;
+				plen -= 4;
+			}
+
+			// data packet
+			case 0x60: {
+				seqno = ntohs(*(u16_t*)(pktp+2));
+				rtptime = ntohl(*(u32_t*)(pktp+4));
+
+				// adjust pointer and length
+				pktp += 12;
+				plen -= 12;
+
+				LOG_SDEBUG("[%p]: seqno:%hu rtp:%u (type: %x, first: %u)", ctx, seqno, rtptime, type, packet[1] & 0x80);
+
+				// check if packet contains enough content to be reasonable
+				if (plen < 16) break;
+
+				if ((packet[1] & 0x80) && (type != 0x56)) {
+					LOG_INFO("[%p]: 1st audio packet received", ctx);
+				}
+
+				buffer_put_packet(ctx, seqno, rtptime, packet[1] & 0x80, pktp, plen);
+
+				break;
+			}
+
+			// sync packet
+			case 0x54: {
+				u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
+				u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
+				u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
+				u16_t flags = ntohs(*(u16_t*)(pktp+2));
+				u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
+
+				// something is wrong and if we are supposed to be NTP synced, better ask for re-sync
+				if (remote_gap > 10000) {
+					if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);
+					LOG_WARN("discarding remote timing information %u", remote_gap);
+					break;
+				}
+
+				pthread_mutex_lock(&ctx->ab_mutex);
+
+				// re-align timestamp and expected local playback time (and magic 11025 latency)
+				ctx->latency = rtp_now - rtp_now_latency;
+				if (flags == 7 || flags == 4) ctx->latency += 11025;
+				if (ctx->latency < MIN_LATENCY) ctx->latency = MIN_LATENCY;
+				else if (ctx->latency > MAX_LATENCY) ctx->latency = MAX_LATENCY;
+				ctx->synchro.rtp = rtp_now - ctx->latency;
+				ctx->synchro.time = ctx->timing.local + remote_gap;
+
+				// now we are synced on RTP frames
+				ctx->synchro.status |= RTP_SYNC;
+
+				// 1st sync packet received (signals a restart of playback)
+				if (packet[0] & 0x10) {
+					LOG_INFO("[%p]: 1st sync packet received", ctx);
+				}
+
+				pthread_mutex_unlock(&ctx->ab_mutex);
+
+				LOG_DEBUG("[%p]: sync packet latency:%d rtp_latency:%u rtp:%u remote ntp:%llx, local time:%u local rtp:%u (now:%u)",
+						  ctx, ctx->latency, rtp_now_latency, rtp_now, remote, ctx->synchro.time, ctx->synchro.rtp, gettime_ms());
+
+				if (!count--) {
+					rtp_request_timing(ctx);
+					count = 3;
+				}
+
+				if ((ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) ctx->cmd_cb(RAOP_TIMING);
+
+				break;
+			}
+
+			// NTP timing packet
+			case 0x53: {
+				u64_t expected;
+				u32_t reference   = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
+				u64_t remote 	  =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
+				u32_t roundtrip   = gettime_ms() - reference;
+
+				// better discard sync packets when roundtrip is suspicious and ask for another one
+				if (roundtrip > 100) {
+					rtp_request_timing(ctx);
+					LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
+					break;
+				}
+
+				/*
+				  The expected elapsed remote time should be exactly the same as
+				  elapsed local time between the two request, corrected by the
+				  drifting
+				*/
+				expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
+
+				ctx->timing.remote = remote;
+				ctx->timing.local = reference;
+
+				// now we are synced on NTP (mutex not needed)
+				ctx->synchro.status |= NTP_SYNC;
+
+				LOG_DEBUG("[%p]: Timing references local:%llu, remote:%llx (delta:%lld, sum:%lld, adjust:%lld, gaps:%d)",
+						  ctx, ctx->timing.local, ctx->timing.remote);
+
+				break;
+			}
+			
+			default: {
+				LOG_WARN("Unknown packet received %x", (int) type);
+				break;
+			}
+		}
+	}
+
+	free(packet);
+	LOG_INFO("[%p]: terminating", ctx);
+
+#ifndef WIN32
+	xTaskNotifyGive(ctx->joiner);
+	vTaskSuspend(NULL);
+#endif
+
+	return NULL;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_timing(rtp_t *ctx) {
+	unsigned char req[32];
+	u32_t now = gettime_ms();
+	int i;
+	struct sockaddr_in host;
+
+	LOG_DEBUG("[%p]: timing request now:%u (port: %hu)", ctx, now, ctx->rtp_sockets[TIMING].rport);
+
+	req[0] = 0x80;
+	req[1] = 0x52|0x80;
+	*(u16_t*)(req+2) = htons(7);
+	*(u32_t*)(req+4) = htonl(0);  // dummy
+	for (i = 0; i < 16; i++) req[i+8] = 0;
+	*(u32_t*)(req+24) = 0;
+	*(u32_t*)(req+28) = htonl(now); // this is not a real NTP, but a 32 ms counter in the low part of the NTP
+
+	if (ctx->host.s_addr != INADDR_ANY) {
+		host.sin_family = AF_INET;
+		host.sin_addr =	ctx->host;
+	} else host = ctx->rtp_host;
+
+	// no address from sender, need to wait for 1st packet to be received
+	if (host.sin_addr.s_addr == INADDR_ANY) return false;
+
+	host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
+
+	if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &host, sizeof(host))) {
+		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+	}
+
+	return true;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
+	unsigned char req[8];    // *not* a standard RTCP NACK
+
+	// do not request silly ranges (happens in case of network large blackouts)
+	if (seq_order(last, first) || last - first > BUFFER_FRAMES / 2) return false;
+	
+	ctx->resent_req += last - first + 1;
+
+	LOG_DEBUG("resend request [W:%hu R:%hu first=%hu last=%hu]", ctx->ab_write, ctx->ab_read, first, last);
+
+	req[0] = 0x80;
+	req[1] = 0x55|0x80;  // Apple 'resend'
+	*(u16_t*)(req+2) = htons(1);  // our seqnum
+	*(u16_t*)(req+4) = htons(first);  // missed seqnum
+	*(u16_t*)(req+6) = htons(last-first+1);  // count
+
+	ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
+
+	if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
+		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+	}
+
+	return true;
+}
+

+ 22 - 9
components/raop/util.c

@@ -23,12 +23,7 @@
 #ifdef WIN32
 #include <iphlpapi.h>
 #else
-/*
-#include <sys/ioctl.h>
-#include <net/if.h>
-#include <net/if_arp.h>
-#include <netdb.h>
-*/
+#include "tcpip_adapter.h"
 #include <ctype.h>
 #endif
 
@@ -84,8 +79,26 @@ in_addr_t get_localhost(char **name)
 	}
 	else return INADDR_ANY;
 #else
-	// missing platform here ...
-	return INADDR_ANY;
+	tcpip_adapter_ip_info_t ipInfo; 
+	tcpip_adapter_if_t if_type = TCPIP_ADAPTER_IF_STA;
+
+	// then get IP address
+ 	tcpip_adapter_get_ip_info(if_type, &ipInfo);
+	
+	// we might be in AP mode
+	if (ipInfo.ip.addr == INADDR_ANY) {
+		if_type = TCPIP_ADAPTER_IF_AP;
+		tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo);
+	}
+
+	// get hostname if required
+	if (name) {
+		const char *hostname;
+		tcpip_adapter_get_hostname(if_type, &hostname);
+		*name = strdup(hostname);
+	}	
+
+	return ipInfo.ip.addr;
 #endif
 }
 
@@ -401,7 +414,7 @@ bool http_parse(int sock, char *method, key_data_t *rkd, char **body, int *len)
 	}
 
 	if (*len) {
-		int size = 0;
+		int size = 0;
 
 		*body = malloc(*len + 1);
 		while (*body && size < *len) {

+ 4 - 2
components/services/buttons.c

@@ -191,7 +191,7 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
 
 	if (n_buttons >= MAX_BUTTONS) return;
 
-	ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %u", gpio, type, pull, long_press, shifter_gpio);
+	ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %d", gpio, type, pull, long_press, shifter_gpio);
 
 	if (!n_buttons) {
 		button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
@@ -209,7 +209,6 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
  	buttons[n_buttons].debounce = debounce ? debounce: DEBOUNCE;
 	buttons[n_buttons].handler = handler;
 	buttons[n_buttons].long_press = long_press;
-	buttons[n_buttons].level = -1;
 	buttons[n_buttons].shifter_gpio = shifter_gpio;
 	buttons[n_buttons].type = type;
 	buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer);
@@ -247,6 +246,9 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
 	
 	// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
 	if (gpio == 36 || gpio == 39) gpio36_39_used = true;
+	
+	// and initialize level ...
+	buttons[n_buttons].level = gpio_get_level(gpio);
 
 	gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
 	gpio_intr_enable(gpio);

+ 2 - 1
components/services/monitor.c

@@ -116,7 +116,8 @@ static void jack_handler_default(void *id, button_event_e event, button_press_e
  * 
  */
 bool jack_inserted_svc (void) {
-	return button_is_pressed(jack.gpio, NULL);
+	if (jack.gpio != -1) return button_is_pressed(jack.gpio, NULL);
+	else return true;
 }
 
 /****************************************************************************************

+ 24 - 24
components/squeezelite/a1s/ac101.c

@@ -35,7 +35,7 @@
 
 const static char TAG[] = "AC101";
 
-#define SPKOUT_EN ((1 << 11) | (1 << 7))
+#define SPKOUT_EN ((1 << 9) | (1 << 11) | (1 << 7) | (1 << 5))
 #define EAROUT_EN ((1 << 11) | (1 << 12) | (1 << 13))
 #define BIN(a,b,c,d)	0b##a##b##c##d
 
@@ -132,8 +132,8 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
 	//Path Configuration
 	i2c_write_reg(DAC_MXR_SRC, 		BIN(1000,1000,0000,0000));	// DAC from I2S
 	i2c_write_reg(DAC_DIG_CTRL, 	BIN(1000,0000,0000,0000));	// enable DAC
-	i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111,0000,0000,000));	// enable DAC/Analogue (see note on offset removal and PA)
-	i2c_write_reg(OMIXER_DACA_CTRL, BIN(1100,0000,0000,000));	// enable DAC/Analogue (see note on offset removal and PA)
+	i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111,0000,0000,0000));	// enable DAC/Analogue (see note on offset removal and PA)
+	i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111,1111,0000,0000));	// this toggle is needed for headphone PA offset
 #if ENABLE_ADC	
 	i2c_write_reg(OMIXER_SR, 		BIN(0000,0001,0000,0010));	// source=DAC(R/L) (are DACR and DACL really inverted in bitmap?)
 #else
@@ -149,8 +149,7 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
 	
 	// enable earphone & speaker
 	i2c_write_reg(SPKOUT_CTRL, 0x0220);
-	i2c_write_reg(OMIXER_DACA_CTRL, 0xff00);
-	i2c_write_reg(HPOUT_CTRL, 0x3801);
+	i2c_write_reg(HPOUT_CTRL, 0xf801);
 	
 	// set gain for speaker and earphone
 	ac101_set_spk_volume(100);
@@ -206,9 +205,10 @@ static void speaker(bool active) {
  * headset
  */
 static void headset(bool active) {
+	// there might be  aneed to toggle OMIXER_DACA_CTRL 11:8, not sure
 	uint16_t value = i2c_read_reg(HPOUT_CTRL);
 	if (active) i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN);
-	else i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN);			
+	else i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN);		
 } 	
 
 /****************************************************************************************
@@ -284,14 +284,14 @@ static int ac101_get_spk_volume(void) {
  * Set normalized (0..100) volume
  */
 static void ac101_set_spk_volume(uint8_t volume) {
-	volume = max(volume, 0x1f);
-	volume = ((int) volume * 0x1f) / 100;
-	volume |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f;
-	i2c_write_reg(SPKOUT_CTRL, volume);
+	uint16_t value = max(volume, 100);
+	value = ((int) value * 0x1f) / 100;
+	value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f;
+	i2c_write_reg(SPKOUT_CTRL, value);
 }
 
 /****************************************************************************************
- * Get normalized (0..100) earphonz volume
+ * Get normalized (0..100) earphone volume
  */
 static int ac101_get_earph_volume(void) {
 	return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
@@ -301,10 +301,10 @@ static int ac101_get_earph_volume(void) {
  * Set normalized (0..100) earphone volume
  */
 static void ac101_set_earph_volume(uint8_t volume) {
-	volume = max(volume, 0x3f);
-	volume = (((int) volume * 0x3f) / 100) << 4;
-	volume |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4);
-	i2c_write_reg(HPOUT_CTRL, volume);
+	uint16_t value = max(volume, 100);
+	value = (((int) value * 0x3f) / 100) << 4;
+	value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4);
+	i2c_write_reg(HPOUT_CTRL, value);
 }
 
 /****************************************************************************************
@@ -345,17 +345,16 @@ static void ac101_start(ac_module_t mode) {
 		i2c_write_reg(0x50, 0x3bc0);
     }
     if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
-		//I2S1_SDOUT_CTRL
-		//i2c_write_reg(PLL_CTRL2, 0x8120);
+		// I2S1_SDOUT_CTRL
+		// i2c_write_reg(PLL_CTRL2, 0x8120);
     	i2c_write_reg(0x04, 0x800c);
     	i2c_write_reg(0x05, 0x800c);
-		//res |= i2c_write_reg(0x06, 0x3000);
+		// res |= i2c_write_reg(0x06, 0x3000);
     }
     if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
-		headset(true);
-		speaker(true);
-		ac101_set_earph_volume(100);
-		ac101_set_spk_volume(100);
+		uint16_t value = i2c_read_reg(PLL_CTRL2);
+		value |= 0x8000;
+		i2c_write_reg(PLL_CTRL2, value);
     }
 }
 
@@ -363,8 +362,9 @@ static void ac101_start(ac_module_t mode) {
  * 
  */
 static void ac101_stop(void) {
-	speaker(false);
-	headset(false);	
+	uint16_t value = i2c_read_reg(PLL_CTRL2);
+	value &= ~0x8000;
+	i2c_write_reg(PLL_CTRL2, value);
 }
 
 /****************************************************************************************

+ 2 - 0
components/squeezelite/output_i2s.c

@@ -274,6 +274,8 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 	if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
 	else adac->speaker(true);
 	
+	adac->headset(jack_inserted_svc());
+	
 	parse_set_GPIO(set_amp_gpio);
 		
 	esp_pthread_cfg_t cfg = esp_pthread_get_default_config();

+ 1 - 1
plugin/SqueezeESP32/PlayerSettings.pm

@@ -21,7 +21,7 @@ sub needsClient {
 
 sub validFor {
 	my ($class, $client) = @_;
-	return $client->model eq 'squeezeesp32';
+	return $client->model eq 'squeezeesp32' && $client->displayWidth;
 }
 
 sub page {

+ 56 - 0
squeezelite.cmake

@@ -0,0 +1,56 @@
+
+
+if(NOT SDKCONFIG OR NOT IDF_PATH  OR NOT IDF_TARGET )
+    message(FATAL_ERROR "squeezelite should not be made outside of the main project !")
+endif()
+
+function(___register_flash target_name sub_type)
+	partition_table_get_partition_info(otaapp_offset "--partition-type app --partition-subtype ${sub_type}" "offset")
+	esptool_py_flash_project_args(${target_name} ${otaapp_offset} ${build_dir}/${target_name}.bin FLASH_IN_PROJECT)
+	esptool_py_custom_target(${target_name}-flash ${target_name} "${target_name}")
+endfunction()
+
+function(___create_new_target target_name)
+	idf_build_get_property(build_dir BUILD_DIR)
+	set(target_elf ${target_name}.elf)
+	
+	# Create a dummy file to work around CMake requirement of having a source
+	# file while adding an executable
+	set(target_elf_src ${CMAKE_BINARY_DIR}/${target_name}_src.c)
+	add_custom_command(OUTPUT ${target_elf_src}
+		COMMAND ${CMAKE_COMMAND} -E touch ${target_elf_src}
+	    VERBATIM)
+	
+	
+	add_custom_target(_${target_name}_elf DEPENDS "${target_elf_src}"  )
+	add_executable(${target_elf} "${target_elf_src}")
+	add_dependencies(${target_elf} _${target_name}_elf)
+	add_dependencies(${target_elf} "recovery.elf")
+	set_property(TARGET ${target_elf} PROPERTY RECOVERY_BUILD 0 )
+	set_property(TARGET ${target_elf} PROPERTY RECOVERY_PREFIX app_${target_name})
+	idf_build_get_property(bca BUILD_COMPONENT_ALIASES)            
+	target_link_libraries(${target_elf} ${bca})
+	set(target_name_mapfile "${target_name}.map")
+	target_link_libraries(${target_elf} "-Wl,--cref -Wl,--Map=${CMAKE_BINARY_DIR}/${target_name_mapfile}")
+	add_custom_command(
+			TARGET ${target_elf}
+			POST_BUILD 
+	        COMMAND ${CMAKE_COMMAND} -E echo "Generated ${build_dir}/${target_name}.bin"
+		    #COMMAND echo ${ESPTOOLPY} elf2image ${ESPTOOLPY_FLASH_OPTIONS} ${esptool_elf2image_args}
+		    #COMMAND echo ${ESPTOOLPY} elf2image ${ESPTOOLPY_FLASH_OPTIONS} ${ESPTOOLPY_ELF2IMAGE_OPTIONS}
+	        COMMAND ${ESPTOOLPY} elf2image ${ESPTOOLPY_FLASH_OPTIONS} ${ESPTOOLPY_ELF2IMAGE_OPTIONS}
+	            -o "${build_dir}/${target_name}.bin" "${target_name}.elf"
+	        DEPENDS "${target_name}.elf" 
+	        WORKING_DIRECTORY ${build_dir}
+	        COMMENT "Generating binary image from built executable"
+	        VERBATIM
+	)
+	set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
+        ADDITIONAL_MAKE_CLEAN_FILES
+        "${build_dir}/${target_name_mapfile}" "${build_dir}/${target_elf_src}" )
+        
+
+endfunction()
+
+___create_new_target(squeezelite )
+___register_flash(squeezelite ota_0)