Kaynağa Gözat

Finalize buttons for AirPlay and BT, worked on coexistence

philippe44 5 yıl önce
ebeveyn
işleme
540466e746

+ 3 - 3
components/audio_controls/audio_controls.c

@@ -86,7 +86,7 @@ void down(void *id, button_event_e event, button_press_e press, bool longpress)
 /****************************************************************************************
  * 
  */
-void actrls_init(int n, actrls_config_t *config) {
+void actrls_init(int n, const actrls_config_t *config) {
 	for (int i = 0; i < n; i++) {
 		button_create(config + i, config[i].gpio, config[i].type, config[i].pull, control_handler, config[i].long_press, config[i].shifter_gpio);
 	}
@@ -95,7 +95,7 @@ void actrls_init(int n, actrls_config_t *config) {
 /****************************************************************************************
  * 
  */
-void actrls_set_default(actrls_t controls) {
+void actrls_set_default(const actrls_t controls) {
 	memcpy(default_controls, controls, sizeof(actrls_t));
 	memcpy(current_controls, default_controls, sizeof(actrls_t));
 }
@@ -103,7 +103,7 @@ void actrls_set_default(actrls_t controls) {
 /****************************************************************************************
  * 
  */
-void actrls_set(actrls_t controls) {
+void actrls_set(const actrls_t controls) {
 	memcpy(current_controls, controls, sizeof(actrls_t));
 }
 

+ 3 - 3
components/audio_controls/audio_controls.h

@@ -36,7 +36,7 @@ typedef struct {
 	actrls_action_e normal[2], longpress[2], shifted[2], longshifted[2];	// [0] keypressed, [1] keyreleased
 } actrls_config_t;
 
-void actrls_init(int n, actrls_config_t *config);
-void actrls_set_default(actrls_t controls);
-void actrls_set(actrls_t controls);
+void actrls_init(int n, const actrls_config_t *config);
+void actrls_set_default(const actrls_t controls);
+void actrls_set(const actrls_t controls);
 void actrls_unset(void);

+ 9 - 6
components/audio_controls/buttons.c

@@ -36,10 +36,11 @@ static const char * TAG = "audio_controls";
 
 static int n_buttons = 0;
 
-#define MAX_BUTTONS		16
-#define DEBOUNCE		50
+#define BUTTON_STACK_SIZE	4096
+#define MAX_BUTTONS			16
+#define DEBOUNCE			50
 
-static struct button_s {
+static EXT_RAM_ATTR struct button_s {
 	void *id;
 	int gpio, index;
 	button_handler handler;
@@ -95,7 +96,7 @@ static void buttons_timer( TimerHandle_t xTimer ) {
  */
 static void buttons_task(void* arg) {
 	ESP_LOGI(TAG, "starting button tasks");
-
+	
     while (1) {
 		struct button_s button;
 		button_event_e event;
@@ -149,7 +150,9 @@ void dummy_handler(void *id, button_event_e event, button_press_e press) {
  * Create buttons 
  */
 void button_create(void *id, int gpio, int type, bool pull, button_handler handler, int long_press, int shifter_gpio) { 
-	
+	static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
+	static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
+
 	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);
@@ -157,7 +160,7 @@ void button_create(void *id, int gpio, int type, bool pull, button_handler handl
 	if (!n_buttons) {
 		button_evt_queue = xQueueCreate(10, sizeof(struct button_s));
 		gpio_install_isr_service(0);
-		xTaskCreate(buttons_task, "buttons_task", 8192, NULL, ESP_TASK_PRIO_MIN + 1, NULL);
+		xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
 	}
 	
 	// just in case this structure is allocated in a future release

+ 18 - 21
components/driver_bt/bt_app_sink.c

@@ -49,7 +49,7 @@ enum {
 };
 char * bt_name = NULL;
 
-static void (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...);
+static bool (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...);
 static void (*bt_app_a2d_data_cb)(const uint8_t *data, uint32_t len);
 
 /* handler for bluetooth stack enabled events */
@@ -84,42 +84,35 @@ static void bt_volume_down(void) {
 	// volume UP/DOWN buttons are not supported by iPhone/Android
 	volume_set_by_local_host(s_volume > 3 ? s_volume - 3 : 0);
 	(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
-	ESP_LOGD(BT_AV_TAG, "BT volume down %u", s_volume);
 }
 
 static void bt_toggle(void) {
 	if (s_play_status != ESP_AVRC_PLAYBACK_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	else esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	//s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED;
-	ESP_LOGD(BT_AV_TAG, "BT play/pause toggle %u", s_volume);
 }
 
 static void bt_play(void) {
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
-	ESP_LOGD(BT_AV_TAG, "BT play");
 }
 
 static void bt_pause(void) {
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
-	ESP_LOGD(BT_AV_TAG, "BT pause");
 }
 
 static void bt_stop(void) {
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
-	ESP_LOGD(BT_AV_TAG, "BT stop");
 }
 
 static void bt_prev(void) {
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
-	ESP_LOGD(BT_AV_TAG, "BT previous");
 }
 
 static void bt_next(void) {
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
-	ESP_LOGD(BT_AV_TAG, "BT next");
 }
 
-static actrls_t controls = {
+const static actrls_t controls = {
 	bt_volume_up, bt_volume_down,	// volume up, volume down
 	bt_toggle, bt_play,	// toggle, play
 	bt_pause, bt_stop,	// pause, stop
@@ -127,16 +120,18 @@ static actrls_t controls = {
 	bt_prev, bt_next,	// prev, next
 };
 
-void bt_sink_cmd(bt_sink_cmd_t event, ...) {
-	switch(event) {
-	case BT_SINK_DISCONNECTED:
-		esp_a2d_sink_disconnect(s_remote_bda);
-		actrls_unset();
-		break;
-	default:
-		ESP_LOGW(BT_AV_TAG, "unhandled command %u", event);
-		break;
-	}
+/* taking/giving audio system's control */
+void bt_master(bool on) {
+	if (on) actrls_set(controls);
+	else actrls_unset();
+}
+
+/* disconnection */
+void bt_disconnect(void) {
+	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
+	esp_a2d_sink_disconnect(s_remote_bda);
+	actrls_unset();
+	ESP_LOGI(BT_AV_TAG, "forced disconnection");
 }
 
 /* callback for A2DP sink */
@@ -218,8 +213,10 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
         } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
 			memcpy(s_remote_bda, bda, 6);
             esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
-			(*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED);
-			actrls_set(controls);
+			if (!(*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED)){
+				esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
+				esp_a2d_sink_disconnect(s_remote_bda);
+			}
         }
         break;
     }

+ 8 - 3
components/driver_bt/bt_app_sink.h

@@ -14,7 +14,7 @@
 typedef enum { 	BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, 
 				BT_SINK_RATE, BT_SINK_VOLUME,  } bt_sink_cmd_t;
 				
-typedef void (*bt_cmd_cb_t)(bt_sink_cmd_t cmd, ...);
+typedef bool (*bt_cmd_cb_t)(bt_sink_cmd_t cmd, ...);
 typedef void (*bt_data_cb_t)(const uint8_t *data, uint32_t len);
 
 /**
@@ -28,8 +28,13 @@ void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb);
 void bt_sink_deinit(void);
 
 /**
- * @brief     local command mode (stop, play, volume ...)
+ * @brief     * @brief     do what's necessary when becoming in charge
  */
-void bt_sink_cmd(bt_sink_cmd_t event, ...);
+void bt_master(bool on);
+
+/**
+ * @brief     force disconnection
+ */
+void bt_disconnect(void);
 
 #endif /* __BT_APP_SINK_H__*/

+ 170 - 60
components/raop/raop.c

@@ -43,7 +43,8 @@
 #include "dmap_parser.h"
 #include "log_util.h"
 
-#define RTSP_STACK_SIZE (8*1024)
+#define RTSP_STACK_SIZE 	(8*1024)
+#define SEARCH_STACK_SIZE	(2*1048)
 
 typedef struct raop_ctx_s {
 #ifdef WIN32
@@ -60,7 +61,7 @@ typedef struct raop_ctx_s {
 #else
 	TaskHandle_t thread, search_thread, joiner;
 	StaticTask_t *xTaskBuffer;
-    StackType_t *xStack;
+	StackType_t xStack[RTSP_STACK_SIZE] __attribute__ ((aligned (4)));
 #endif
 	unsigned char mac[6];
 	int latency;
@@ -71,14 +72,19 @@ typedef struct raop_ctx_s {
 	struct rtp_s *rtp;
 	raop_cmd_cb_t	cmd_cb;
 	raop_data_cb_t	data_cb;
-	/*
 	struct {
 		char				DACPid[32], id[32];
 		struct in_addr		host;
 		u16_t				port;
+#ifdef WIN32
 		struct mDNShandle_s *handle;
+#else
+		bool running;
+		TaskHandle_t thread, joiner;
+		StaticTask_t *xTaskBuffer;
+		StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
+#endif
 	} active_remote;
-	*/
 	void *owner;
 } raop_ctx_t;
 
@@ -98,7 +104,7 @@ static void* 	search_remote(void *args);
 extern char private_key[];
 
enum { RSA_MODE_KEY, RSA_MODE_AUTH };
 
-
static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
+static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
 
 /*----------------------------------------------------------------------------*/
 struct raop_ctx_s *raop_create(struct in_addr host, char *name,
@@ -186,26 +192,23 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
 	ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
 	
     ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
-    ctx->xStack = (StackType_t*) malloc(RTSP_STACK_SIZE);
 	ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtsp_thread, "RTSP_thread", RTSP_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->xStack, ctx->xTaskBuffer);
 #endif
 
 	return ctx;
 }
 
-
 /*----------------------------------------------------------------------------*/
 void raop_delete(struct raop_ctx_s *ctx) {
+#ifdef WIN32
 	int sock;
 	struct sockaddr addr;
 	socklen_t nlen = sizeof(struct sockaddr);
-
-	if (!ctx) return;
-	
-#if !defined WIN32
-	ctx->joiner = xTaskGetCurrentTaskHandle();
 #endif
+	
+if (!ctx) return;
 
+#ifdef WIN32
 	ctx->running = false;
 
 	// wake-up thread by connecting socket, needed for freeBSD
@@ -214,66 +217,99 @@ void raop_delete(struct raop_ctx_s *ctx) {
 	connect(sock, (struct sockaddr*) &addr, sizeof(addr));
 	closesocket(sock);
 
-#ifdef WIN32
 	pthread_join(ctx->thread, NULL);
-#else
-	xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
-	vTaskDelete(ctx->thread);
-	free(ctx->xStack);
-	heap_caps_free(ctx->xTaskBuffer);
-#endif
 
 	rtp_end(ctx->rtp);
 
-#ifdef WIN32
 	shutdown(ctx->sock, SD_BOTH);
-#else
-	shutdown(ctx->sock, SHUT_RDWR);
-#endif
 	closesocket(ctx->sock);
 
-	/*
 	// terminate search, but do not reclaim memory of pthread if never launched
 	if (ctx->active_remote.handle) {
 		close_mDNS(ctx->active_remote.handle);
 		pthread_join(ctx->search_thread, NULL);
 	}
-	*/
 
-	NFREE(ctx->rtsp.aeskey);
-	NFREE(ctx->rtsp.aesiv);
-	NFREE(ctx->rtsp.fmtp);
-	
 	// stop broadcasting devices
-#ifdef WIN32
 	mdns_service_remove(ctx->svr, ctx->svc);
 	mdnsd_stop(ctx->svr);
-#else
+#else 
+	// first stop the search task if any
+	if (ctx->active_remote.running) {
+		ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
+		ctx->active_remote.running = false;
+
+		vTaskResume(ctx->active_remote.thread);
+		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+		vTaskDelete(ctx->active_remote.thread);
+
+		heap_caps_free(ctx->active_remote.xTaskBuffer);
+	}
+
+	// then the RTSP task
+	ctx->joiner = xTaskGetCurrentTaskHandle();
+	ctx->running = false;
+
+	ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+	vTaskDelete(ctx->thread);
+	heap_caps_free(ctx->xTaskBuffer);
+
+	rtp_end(ctx->rtp);
+
+	shutdown(ctx->sock, SHUT_RDWR);
+	closesocket(ctx->sock);
+		
 	mdns_service_remove("_raop", "_tcp");	
 #endif
 
+	NFREE(ctx->rtsp.aeskey);
+	NFREE(ctx->rtsp.aesiv);
+	NFREE(ctx->rtsp.fmtp);
+
 	free(ctx);
 }
 
-
 /*----------------------------------------------------------------------------*/
-void  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
-/*
+void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
 	struct sockaddr_in addr;
 	int sock;
 	char *command = NULL;
 
 	// first notify the remote controller (if any)
 	switch(event) {
+		case RAOP_REW:
+			command = strdup("beginrew");
+			break;
+		case RAOP_FWD:
+			command = strdup("beginff");
+			break;
+		case RAOP_PREV:
+			command = strdup("previtem");
+			break;
+		case RAOP_NEXT:
+			command = strdup("nextitem");
+			break;
+		case RAOP_TOGGLE:
+			command = strdup("playpause");
+			break;
 		case RAOP_PAUSE:
 			command = strdup("pause");
 			break;
 		case RAOP_PLAY:
 			command = strdup("play");
 			break;
+		case RAOP_RESUME:
+			command = strdup("playresume");
+			break;
 		case RAOP_STOP:
 			command = strdup("stop");
 			break;
+		case RAOP_VOLUME_UP:
+			command = strdup("volumeup");
+			break;
+		case RAOP_VOLUME_DOWN:
+			command = strdup("volumedown");
+			break;
 		case RAOP_VOLUME: {
 			float Volume = *((float*) param);
 			Volume = Volume ? (Volume - 1) * 30 : -144;
@@ -286,9 +322,9 @@ void  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
 
 	// no command to send to remote or no remote found yet
 	if (!command || !ctx->active_remote.port) {
-
		NFREE(command);
-
		return;
-
	}
+		NFREE(command);
+		return;
+	}
 
 	sock = socket(AF_INET, SOCK_STREAM, 0);
 
@@ -317,11 +353,7 @@ void  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
 	}
 
 	free(command);
-
 	closesocket(sock);
-*/
-	// then notify local system
-	ctx->cmd_cb(event, param);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -366,7 +398,7 @@ static void *rtsp_thread(void *arg) {
 	if (sock != -1) closesocket(sock);
 
 #ifndef WIN32
-	xTaskNotify(ctx->joiner, 0, eNoAction);
+	xTaskNotifyGive(ctx->joiner);
 	vTaskSuspend(NULL);
 #endif
 
@@ -429,12 +461,27 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		NFREE(ctx->rtsp.aesiv);
 		NFREE(ctx->rtsp.fmtp);
 		
-		// LMS might has taken over the player, leaving us with a running RTP session
+		// LMS might has taken over the player, leaving us with a running RTP session (should not happen)
 		if (ctx->rtp) {
-			LOG_INFO("[%p]: closing unfinished RTP session", ctx);
+			LOG_WARN("[%p]: closing unfinished RTP session", ctx);
 			rtp_end(ctx->rtp);
 		}	
 
+		// same, should not happen unless we have missed a teardown ...
+		if (ctx->active_remote.running) {
+			ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
+			ctx->active_remote.running = false;
+			
+			vTaskResume(ctx->active_remote.thread);
+			ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+			vTaskDelete(ctx->active_remote.thread);
+
+			heap_caps_free(ctx->active_remote.xTaskBuffer);
+			memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
+
+			LOG_WARN("[%p]: closing unfinished mDNS search", ctx);
+		}
+
 		if ((p = strcasestr(body, "rsaaeskey")) != NULL) {
 			unsigned char *aeskey;
 			int len, outlen;
@@ -467,13 +514,17 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		}
 
 		// on announce, search remote
-		/*
 		if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
 		if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
 
+#ifdef WIN32	
 		ctx->active_remote.handle = init_mDNS(false, ctx->host);
 		pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
-		*/
+#else
+		ctx->active_remote.running = true;
+		ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
+		ctx->active_remote.thread = xTaskCreateStatic( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer);
+#endif		
 
 	} else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
 		char *p;
@@ -481,7 +532,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		short unsigned tport = 0, cport = 0;
 
 		// we are about to stream, do something if needed
-		ctx->cmd_cb(RAOP_SETUP, NULL);
+		success = ctx->cmd_cb(RAOP_SETUP, NULL);
 
 		if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport);
 		if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport);
@@ -520,7 +571,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 
 		if (ctx->rtp) rtp_record(ctx->rtp, seqno, rtptime);
 
-		ctx->cmd_cb(RAOP_STREAM, NULL);
+		success = ctx->cmd_cb(RAOP_STREAM, NULL);
 
 	}  else if (!strcmp(method, "FLUSH")) {
 		unsigned short seqno = 0;
@@ -533,7 +584,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 
 		// only send FLUSH if useful (discards frames above buffer head and top)
 		if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime))
-			ctx->cmd_cb(RAOP_FLUSH, NULL);
+			success = ctx->cmd_cb(RAOP_FLUSH, NULL);
 
 	}  else if (!strcmp(method, "TEARDOWN")) {
 
@@ -541,20 +592,32 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 
 		ctx->rtp = NULL;
 
-		/*
 		// need to make sure no search is on-going and reclaim pthread memory
+#ifdef WIN32
 		if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
 		pthread_join(ctx->search_thread, NULL);
-		memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
-		*/
+#else
+		ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
+		ctx->active_remote.running = false;
+
+		// task might not need to be resumed anyway
+		vTaskResume(ctx->active_remote.thread);
+		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+		vTaskDelete(ctx->active_remote.thread);
 
+		heap_caps_free(ctx->active_remote.xTaskBuffer);
+		
+		LOG_INFO("[%p]: mDNS search task terminated", ctx);
+#endif
+
+		memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
 		NFREE(ctx->rtsp.aeskey);
 		NFREE(ctx->rtsp.aesiv);
 		NFREE(ctx->rtsp.fmtp);
 
-		ctx->cmd_cb(RAOP_STOP, NULL);
+		success = ctx->cmd_cb(RAOP_STOP, NULL);
 
-	} if (!strcmp(method, "SET_PARAMETER")) {
+	} else if (!strcmp(method, "SET_PARAMETER")) {
 		char *p;
 
 		if (body && (p = strcasestr(body, "volume")) != NULL) {
@@ -563,7 +626,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 			sscanf(p, "%*[^:]:%f", &volume);
 			LOG_INFO("[%p]: SET PARAMETER volume %f", ctx, volume);
 			volume = (volume == -144.0) ? 0 : (1 + volume / 30);
-			ctx->cmd_cb(RAOP_VOLUME, &volume);
+			success = ctx->cmd_cb(RAOP_VOLUME, &volume);
 		}
 /*
 		if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) {
@@ -585,7 +648,6 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 	}
 
 	// don't need to free "buf" because kd_lookup return a pointer, not a strdup
-
 	kd_add(resp, "Audio-Jack-Status", "connected; type=analog");
 	kd_add(resp, "CSeq", kd_lookup(headers, "CSeq"));
 
@@ -605,7 +667,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 }
 
 /*----------------------------------------------------------------------------*/
-/*
+#ifdef WIN32
 bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
 	mDNSservice_t *s;
 	raop_ctx_t *ctx = (raop_ctx_t*) cookie;
@@ -625,11 +687,9 @@ bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
 	// let caller clear list
 	return false;
 }
-*/
 
 
 /*----------------------------------------------------------------------------*/
-/*
 static void* search_remote(void *args) {
 	raop_ctx_t *ctx = (raop_ctx_t*) args;
 
@@ -637,8 +697,58 @@ static void* search_remote(void *args) {
 
 	return NULL;
 }
-
*/
 
+#else
+
+/*----------------------------------------------------------------------------*/
+static void* search_remote(void *args) {
+	raop_ctx_t *ctx = (raop_ctx_t*) args;
+	bool found = false;
+	
+	LOG_INFO("starting remote search");
+
+	while (ctx->active_remote.running && !found) {
+		mdns_result_t *results = NULL;
+		mdns_result_t *r;
+		mdns_ip_addr_t *a;
+
+		if (mdns_query_ptr("_dacp", "_tcp", 3000, 32,  &results)) {
+			LOG_ERROR("mDNS active remote query Failed");
+			continue;
+		}
+
+		for (r = results; r && !strcasestr(r->instance_name, ctx->active_remote.DACPid); r = r->next);
+		if (r) {
+			for (a = r->addr; a && a->addr.type != IPADDR_TYPE_V4; a = a->next);
+			if (a) {
+				found = true;
+				ctx->active_remote.host.s_addr = a->addr.u_addr.ip4.addr;
+				ctx->active_remote.port = r->port;
+				LOG_INFO("found remote %s %s:%hu", r->instance_name, inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
+			}
+		}
+	
+		mdns_query_results_free(results);
+	}
+
+	/*
+	for some reason which is beyond me, if that tasks gives the semaphore 
+	before the RTSP tasks waits for it, then freeRTOS crashes in queue 
+	management caused by LWIP stack once the RTSP socket is closed. I have 
+	no clue why, but so we'll suspend the tasks as soon as we're done with 
+	search and wait for the resume then give the semaphore
+	*/
+	// PS: I know this is not fully race-condition free
+	if (ctx->active_remote.running) vTaskSuspend(NULL);
+	xTaskNotifyGive(ctx->active_remote.joiner);
+
+	// now our context will be deleted
+	vTaskSuspend(NULL);
+
+	return NULL;
+ }
+#endif
+
 
/*----------------------------------------------------------------------------*/
 static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode)
 {

+ 1 - 1
components/raop/raop.h

@@ -24,7 +24,7 @@
 #include "platform.h"
 #include "raop_sink.h"
 
-struct raop_ctx_s*   raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency,
+struct raop_ctx_s* raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency,
 							     raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
 void  		  raop_delete(struct raop_ctx_s *ctx);
 void		  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param);

+ 64 - 7
components/raop/raop_sink.c

@@ -13,6 +13,7 @@
 #include "freertos/timers.h"
 #include "nvs_utilities.h"
 #include "raop.h"
+#include "audio_controls.h"
 
 #include "log_util.h"
 
@@ -22,14 +23,68 @@
 #define CONFIG_AIRPLAY_NAME		"ESP32-AirPlay"
 #endif
 
-static const char * TAG = "platform";
-
 log_level	raop_loglevel = lINFO;
 log_level	util_loglevel;
 
 static log_level *loglevel = &raop_loglevel;
 static struct raop_ctx_s *raop;
 
+static void raop_volume_up(void) {
+	raop_cmd(raop, RAOP_VOLUME_UP, NULL);
+	LOG_INFO("AirPlay volume up");
+}
+
+static void raop_volume_down(void) {
+	raop_cmd(raop, RAOP_VOLUME_DOWN, NULL);
+	LOG_INFO("AirPlay volume down");
+}
+
+static void raop_toggle(void) {
+	raop_cmd(raop, RAOP_TOGGLE, NULL);
+	LOG_INFO("AirPlay play/pause");
+}
+
+static void raop_pause(void) {
+	raop_cmd(raop, RAOP_PAUSE, NULL);
+	LOG_INFO("AirPlay pause");
+}
+
+static void raop_play(void) {
+	raop_cmd(raop, RAOP_PLAY, NULL);
+	LOG_INFO("AirPlay play");
+}
+
+static void raop_stop(void) {
+	raop_cmd(raop, RAOP_STOP, NULL);
+	LOG_INFO("AirPlay stop");
+}
+
+static void raop_prev(void) {
+	raop_cmd(raop, RAOP_PREV, NULL);
+	LOG_INFO("AirPlay previous");
+}
+
+static void raop_next(void) {
+	raop_cmd(raop, RAOP_NEXT, NULL);
+	LOG_INFO("AirPlay next");
+}
+
+const static actrls_t controls = {
+	raop_volume_up, raop_volume_down,	// volume up, volume down
+	raop_toggle, raop_play,				// toggle, play
+	raop_pause, raop_stop,				// pause, stop
+	NULL, NULL,							// rew, fwd
+	raop_prev, raop_next,				// prev, next
+};
+
+/****************************************************************************************
+ * Airplay taking/giving audio system's control 
+ */
+void raop_master(bool on) {
+	if (on) actrls_set(controls);
+	else actrls_unset();
+}
+
 /****************************************************************************************
  * Airplay sink de-initialization
  */
@@ -56,14 +111,14 @@ void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
     ESP_ERROR_CHECK( mdns_init() );
     ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
         
-    char * sink_name_buffer= (char *)config_alloc_get(NVS_TYPE_STR, "airplay_name");
+    char * sink_name_buffer= (char *)config_alloc_get(NVS_TYPE_STR,"AirPlay_name");
     if(sink_name_buffer != NULL){
     	memset(sink_name, 0x00, sizeof(sink_name));
     	strncpy(sink_name,sink_name_buffer,sizeof(sink_name)-1 );
     	free(sink_name_buffer);
     }
 
-	ESP_LOGI(TAG, "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
+	LOG_INFO( "mdns hostname set to: [%s] with servicename %s", hostname, sink_name);
 
     // create RAOP instance, latency is set by controller
 	uint8_t mac[6];	
@@ -72,8 +127,10 @@ void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
 }
 
 /****************************************************************************************
- * Airplay local command (stop, start, volume ...)
+ * Airplay forced disconnection
  */
-void raop_sink_cmd(raop_event_t event, void *param) {
-	raop_cmd(raop, event, param);
+void raop_disconnect(void) {
+	LOG_INFO("forced disconnection");
+	raop_cmd(raop, RAOP_STOP, NULL);
+	actrls_unset();
 }

+ 10 - 6
components/raop/raop_sink.h

@@ -13,27 +13,31 @@
 
 #define RAOP_SAMPLE_RATE	44100
 
-typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_PAUSE, RAOP_STOP, RAOP_VOLUME, RAOP_TIMING } raop_event_t ;
+typedef enum { 	RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_PAUSE, RAOP_STOP, 
+				RAOP_VOLUME, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD, 
+				RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ;
 
-typedef void (*raop_cmd_cb_t)(raop_event_t event, void *param);
+typedef bool (*raop_cmd_cb_t)(raop_event_t event, void *param);
 typedef void (*raop_data_cb_t)(const u8_t *data, size_t len, u32_t playtime);
 
 /**
  * @brief     init sink mode (need to be provided)
  */
-
 void raop_sink_init(raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
 
 /**
  * @brief     deinit sink mode (need to be provided)
  */
-
 void raop_sink_deinit(void);
 
 /**
- * @brief     init sink mode (need to be provided)
+ * @brief     do what's necessary when becoming in charge
  */
+void raop_master(bool on);
 
-void raop_sink_cmd(raop_event_t event, void *param);
+/**
+ * @brief     force disconnection
+ */
+void raop_disconnect(void);
 
 #endif /* RAOP_SINK_H*/

+ 4 - 5
components/raop/rtp.c

@@ -139,7 +139,7 @@ typedef struct rtp_s {
 #else
 	TaskHandle_t thread, joiner;
 	StaticTask_t *xTaskBuffer;
-    StackType_t *xStack;
+    StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
 #endif
 
 	struct alac_codec_s *alac_codec;
@@ -275,11 +275,11 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
 #else
 		// xTaskCreate((TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_TASK_SIZE, ctx,  CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1 , &ctx->thread);
 		ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
-		ctx->xStack = (StackType_t*) malloc(RTP_STACK_SIZE);
 		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
 	} else {
+		LOG_ERROR("[%p]: cannot start RTP", ctx);
 		rtp_end(ctx);
 		ctx = NULL;
 	}
@@ -304,9 +304,8 @@ void rtp_end(rtp_t *ctx)
 #ifdef WIN32
 		pthread_join(ctx->thread, NULL);
 #else
-		xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
+		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
 		vTaskDelete(ctx->thread);
-		free(ctx->xStack);
 		heap_caps_free(ctx->xTaskBuffer);
 #endif
 	}
@@ -715,7 +714,7 @@ static void *rtp_thread_func(void *arg) {
 	LOG_INFO("[%p]: terminating", ctx);
 
 #ifndef WIN32
-	xTaskNotify(ctx->joiner, 0, eNoAction);
+	xTaskNotifyGive(ctx->joiner);
 	vTaskSuspend(NULL);
 #endif
 

+ 2 - 2
components/squeezelite/controls.c

@@ -69,7 +69,7 @@ static void lms_next(void) {
 	cli_send_cmd("button fwd");
 }
 
-static actrls_t controls = {
+const static actrls_t controls = {
 	lms_volume_up, lms_volume_down,	// volume up, volume down
 	lms_toggle, lms_play,	// toggle, play
 	lms_pause, lms_stop,	// pause, stop
@@ -102,7 +102,7 @@ static void cli_send_cmd(char *cmd) {
 		LOG_WARN("cannot send CLI %s", packet);
 	}
 
-	close(sock);
+	closesocket(sock);
 }
 
 /****************************************************************************************

+ 4 - 5
components/squeezelite/decode.c

@@ -65,7 +65,7 @@ static void *decode_thread() {
 		toend = (stream.state <= DISCONNECT);
 		UNLOCK_S;
 		LOCK_O;
-		space = _buf_space(outputbuf);
+		space = !output.external ? _buf_space(outputbuf) : 0;
 		UNLOCK_O;
 
 		LOCK_D;
@@ -117,10 +117,6 @@ static void *decode_thread() {
 		}
 	}
 	
-#if EMBEDDED	
-	deregister_external();
-#endif	
-
 	return 0;
 }
 
@@ -246,6 +242,9 @@ void decode_close(void) {
 	pthread_join(thread, NULL);
 #endif
 	mutex_destroy(decode.mutex);
+#if EMBEDDED	
+	deregister_external();
+#endif	
 }
 
 void decode_flush(void) {

+ 44 - 25
components/squeezelite/decode_external.c

@@ -29,7 +29,7 @@
 #define LOCK_D   mutex_lock(decode.mutex);
 #define UNLOCK_D mutex_unlock(decode.mutex);
 
-enum { DECODE_BT = 1, DECODE_AIRPLAY };
+enum { DECODE_BT = 1, DECODE_RAOP };
 
 extern struct outputstate output;
 extern struct decodestate decode;
@@ -60,8 +60,8 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
 {
     size_t bytes, space;
 		
-	// would be better to lock decoder, but really, it does not matter
-	if (decode.state != DECODE_STOPPED) {
+	// would be better to lock output, but really, it does not matter
+	if (!output.external) {
 		LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
 		return;
 	} 
@@ -99,19 +99,18 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
  * BT sink command handler
  */
 
-static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...) 
+static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...) 
 {
 	va_list args;
 	
-	LOCK_D;
-	
-	if (decode.state != DECODE_STOPPED) {
-		LOG_WARN("Cannot use BT sink while LMS is controlling player");
-		UNLOCK_D;
-		bt_sink_cmd(BT_SINK_DISCONNECTED);
-		return;
+	// don't LOCK_O as there is always a chance that LMS takes control later anyway
+	if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
+		LOG_WARN("Cannot use BT sink while LMS/AirPlay is controlling player");
+		return false;
 	} 	
-	
+
+	LOCK_D;
+
 	va_start(args, cmd);
 	
 	if (cmd != BT_SINK_VOLUME) LOCK_O;
@@ -120,12 +119,15 @@ static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
 	case BT_SINK_CONNECTED:
 		output.external = DECODE_BT;
 		output.state = OUTPUT_STOPPED;
+		output.frames_played = 0;
+		_buf_flush(outputbuf);
+		bt_master(true);
 		LOG_INFO("BT sink started");
 		break;
 	case BT_SINK_DISCONNECTED:	
 		if (output.external == DECODE_BT) {
-			output.external = 0;
 			output.state = OUTPUT_OFF;
+			bt_master(false);
 			LOG_INFO("BT sink stopped");
 		}	
 		break;
@@ -155,6 +157,7 @@ static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
 	UNLOCK_D;
 
 	va_end(args);
+	return true;
 }
 
 /****************************************************************************************
@@ -171,15 +174,15 @@ static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t play
 /****************************************************************************************
  * AirPlay sink command handler
  */
-void raop_sink_cmd_handler(raop_event_t event, void *param)
+static bool raop_sink_cmd_handler(raop_event_t event, void *param)
 {
-	LOCK_D;
-	
-	if (decode.state != DECODE_STOPPED) {
-		LOG_WARN("Cannot use Airplay sink while LMS is controlling player");
-		UNLOCK_D;
-		return;
+	// don't LOCK_O as there is always a chance that LMS takes control later anyway
+	if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
+		LOG_WARN("Cannot use Airplay sink while LMS/BT is controlling player");
+		return false;
 	} 	
+
+	LOCK_D;
 	
 	if (event != RAOP_VOLUME) LOCK_O;
 	
@@ -233,6 +236,10 @@ void raop_sink_cmd_handler(raop_event_t event, void *param)
 		case RAOP_SETUP:
 			// we need a fair bit of space for RTP process
 			_buf_resize(outputbuf, RAOP_OUTPUT_SIZE);
+			output.frames_played = 0;
+			output.external = DECODE_RAOP;
+			output.state = OUTPUT_STOPPED;
+			raop_master(true);
 			LOG_INFO("resizing buffer %u", outputbuf->size);
 			break;
 		case RAOP_STREAM:
@@ -242,16 +249,14 @@ void raop_sink_cmd_handler(raop_event_t event, void *param)
 			raop_sync.idx = 0;
 			raop_sync.start = true;		
 			raop_sync.enabled = !strcasestr(output.device, "BT");
-			output.external = DECODE_AIRPLAY;
 			output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
-			output.state = OUTPUT_STOPPED;
 			break;
 		case RAOP_STOP:
 			LOG_INFO("Stop", NULL);
-			output.external = 0;
 			output.state = OUTPUT_OFF;
 			output.frames_played = 0;
 			raop_state = event;
+			raop_master(false);
 			break;
 		case RAOP_FLUSH:
 			LOG_INFO("Flush", NULL);
@@ -286,6 +291,7 @@ void raop_sink_cmd_handler(raop_event_t event, void *param)
 	if (event != RAOP_VOLUME) UNLOCK_O;
 	
 	UNLOCK_D;
+	return true;
 }
 
 /****************************************************************************************
@@ -300,7 +306,7 @@ void register_external(void) {
 	} else {
 		LOG_WARN("Cannot be a BT sink and source");
 	}	
-	if(enable_airplay){
+	if (enable_airplay){
 		raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
 		LOG_INFO("Initializing AirPlay sink");
 	}
@@ -311,8 +317,21 @@ void deregister_external(void) {
 		bt_sink_deinit();
 		LOG_INFO("Stopping BT sink");
 	}
-	if(enable_airplay){
+	if (enable_airplay){
 		raop_sink_deinit();
 		LOG_INFO("Stopping AirPlay sink");
 	}
 }
+
+void decode_resume(int external) {
+	switch (external) {
+	case DECODE_BT:
+		bt_disconnect();
+		break;
+	case DECODE_RAOP:
+		raop_disconnect();
+		raop_state = RAOP_STOP;
+		break;
+	}
+	_buf_resize(outputbuf, output.init_size);
+}

+ 6 - 0
components/squeezelite/embedded.c

@@ -48,3 +48,9 @@ int	pthread_create_name(pthread_t *thread, _CONST pthread_attr_t  *attr,
 uint32_t _gettime_ms_(void) {
 	return (uint32_t) (esp_timer_get_time() / 1000);
 }
+
+extern void cli_controls_init(void);
+
+void embedded_init(void) {
+	cli_controls_init();
+}

+ 3 - 0
components/squeezelite/embedded.h

@@ -43,8 +43,11 @@ uint32_t 	_gettime_ms_(void);
 int			pthread_create_name(pthread_t *thread, _CONST pthread_attr_t  *attr, 
 				   void *(*start_routine)( void * ), void *arg, char *name);
 			
+void		embedded_init(void);
 void 		register_external(void);
 void 		deregister_external(void);
+void 		decode_resume(int external);
+
 void 		(*server_notify)(in_addr_t ip, u16_t hport, u16_t cport);
 				   
 #endif // EMBEDDED_H

+ 1 - 0
components/squeezelite/main.c

@@ -761,6 +761,7 @@ int main(int argc, char **argv) {
 	stream_init(log_stream, stream_buf_size);
 
 #if EMBEDDED
+	embedded_init();
 	output_init_embedded(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle);
 #else
 	if (!strcmp(output_device, "-")) {

+ 0 - 2
components/squeezelite/output_embedded.c

@@ -57,8 +57,6 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz
 	output.start_frames = FRAME_BLOCK;
 	output.rate_delay = rate_delay;
 	
-	cli_controls_init();
-	
 	if (strcasestr(device, "BT ")) {
 		LOG_INFO("init Bluetooth");
 		close_cb = &output_close_bt;

+ 9 - 1
components/squeezelite/slimproto.c

@@ -330,6 +330,12 @@ static void process_strm(u8_t *pkt, int len) {
 			LOCK_O;
 			output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING;
 			output.start_at = jiffies;
+#if EMBEDDED
+			if (output.external) {
+				decode_resume(output.external);
+				output.external = 0;
+			}
+#endif
 			UNLOCK_O;
 
 			LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms());
@@ -373,8 +379,10 @@ static void process_strm(u8_t *pkt, int len) {
 			sendSTAT("STMc", 0);
 			sentSTMu = sentSTMo = sentSTMl = false;
 			LOCK_O;
+#if EMBEDDED
+			if (output.external) decode_resume(output.external);
 			output.external = 0;
-			_buf_resize(outputbuf, output.init_size);
+#endif
 			output.threshold = strm->output_threshold;
 			output.next_replay_gain = unpackN(&strm->replay_gain);
 			output.fade_mode = strm->transition_type - '0';