浏览代码

AirPlay co-existence improvements, couple of display issues

philippe44 5 年之前
父节点
当前提交
4e7ff0a37a

+ 3 - 0
components/display/display.c

@@ -301,16 +301,19 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 		displayer.string[0] = '\0';
 		displayer.string[0] = '\0';
 		displayer.elapsed = displayer.duration = 0;
 		displayer.elapsed = displayer.duration = 0;
 		displayer.offset = displayer.boundary = 0;
 		displayer.offset = displayer.boundary = 0;
+		display_bus(&displayer, DISPLAY_BUS_TAKE);
 		vTaskResume(displayer.task);
 		vTaskResume(displayer.task);
 		break;
 		break;
 	}	
 	}	
 	case DISPLAYER_SUSPEND:		
 	case DISPLAYER_SUSPEND:		
 		// task will display the line 2 from beginning and suspend
 		// task will display the line 2 from beginning and suspend
 		displayer.state = DISPLAYER_IDLE;
 		displayer.state = DISPLAYER_IDLE;
+		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;		
 		break;		
 	case DISPLAYER_SHUTDOWN:
 	case DISPLAYER_SHUTDOWN:
 		// let the task self-suspend (we might be doing i2c_write)
 		// let the task self-suspend (we might be doing i2c_write)
 		displayer.state = DISPLAYER_DOWN;
 		displayer.state = DISPLAYER_DOWN;
+		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;
 		break;
 	case DISPLAYER_TIMER_RUN:
 	case DISPLAYER_TIMER_RUN:
 		if (!displayer.timer) {
 		if (!displayer.timer) {

+ 7 - 1
components/display/display.h

@@ -28,6 +28,9 @@
  So it can conflict with other display direct writes that have been made during
  So it can conflict with other display direct writes that have been made during
  sleep. Note that if DISPLAY_SHUTDOWN has been called meanwhile, it (almost) 
  sleep. Note that if DISPLAY_SHUTDOWN has been called meanwhile, it (almost) 
  never happens
  never happens
+ The display_bus() shall be subscribed by other displayers so that at least
+ when this one (the main) wants to take control over display, it can signal
+ that to others
 */ 
 */ 
   
   
 #define DISPLAY_CLEAR 		0x01
 #define DISPLAY_CLEAR 		0x01
@@ -67,7 +70,10 @@ extern struct display_s {
 	void (*draw_box)( int x1, int y1, int x2, int y2, bool fill);
 	void (*draw_box)( int x1, int y1, int x2, int y2, bool fill);
 } *display;
 } *display;
 
 
+enum display_bus_cmd_e { DISPLAY_BUS_TAKE, DISPLAY_BUS_GIVE };
+bool (*display_bus)(void *from, enum display_bus_cmd_e cmd);
+
 void displayer_scroll(char *string, int speed);
 void displayer_scroll(char *string, int speed);
 void displayer_control(enum displayer_cmd_e cmd, ...);
 void displayer_control(enum displayer_cmd_e cmd, ...);
 void displayer_metadata(char *artist, char *album, char *title);
 void displayer_metadata(char *artist, char *album, char *title);
-void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);
+void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);

+ 5 - 4
components/display/driver_SSD13x6.c

@@ -167,8 +167,8 @@ static void clear(bool full, ...) {
 		va_end(args);
 		va_end(args);
 	}
 	}
 	
 	
+	SSD13x6_display.dirty = true;
 	if (commit)	update();		
 	if (commit)	update();		
-	else SSD13x6_display.dirty = true;
 }	
 }	
 
 
 /****************************************************************************************
 /****************************************************************************************
@@ -247,9 +247,9 @@ static bool line(int num, int x, int attribute, char *text) {
 	ESP_LOGD(TAG, "displaying %s line %u (x:%d, attr:%u)", text, num+1, x, attribute);
 	ESP_LOGD(TAG, "displaying %s line %u (x:%d, attr:%u)", text, num+1, x, attribute);
 	
 	
 	// update whole display if requested
 	// update whole display if requested
+	SSD13x6_display.dirty = true;
 	if (attribute & DISPLAY_UPDATE) update();
 	if (attribute & DISPLAY_UPDATE) update();
-	else SSD13x6_display.dirty = true;
-	
+		
 	return width + x < Display.Width;
 	return width + x < Display.Width;
 }
 }
 
 
@@ -340,8 +340,9 @@ static void text(enum display_font_e font, enum display_pos_e pos, int attribute
 	ESP_LOGD(TAG, "SSDD13x6 displaying %s at %u with attribute %u", text, Anchor, attribute);
 	ESP_LOGD(TAG, "SSDD13x6 displaying %s at %u with attribute %u", text, Anchor, attribute);
 	
 	
 	SSD13x6_FontDrawAnchoredString( &Display, Anchor, text, SSD_COLOR_WHITE );
 	SSD13x6_FontDrawAnchoredString( &Display, Anchor, text, SSD_COLOR_WHITE );
+	
+	SSD13x6_display.dirty = true;
 	if (attribute & DISPLAY_UPDATE) update();
 	if (attribute & DISPLAY_UPDATE) update();
-	else SSD13x6_display.dirty = true;
 	
 	
 	va_end(args);
 	va_end(args);
 }
 }

+ 54 - 44
components/raop/raop.c

@@ -55,7 +55,7 @@ typedef struct raop_ctx_s {
 	short unsigned port;    // RTSP port for AirPlay
 	short unsigned port;    // RTSP port for AirPlay
 	int sock;               // socket of the above
 	int sock;               // socket of the above
 	struct in_addr peer;	// IP of the iDevice (airplay sender)
 	struct in_addr peer;	// IP of the iDevice (airplay sender)
-	bool running;
+	bool running, abort;
 #ifdef WIN32
 #ifdef WIN32
 	pthread_t thread, search_thread;
 	pthread_t thread, search_thread;
 #else
 #else
@@ -83,6 +83,7 @@ typedef struct raop_ctx_s {
 		TaskHandle_t thread, joiner;
 		TaskHandle_t thread, joiner;
 		StaticTask_t *xTaskBuffer;
 		StaticTask_t *xTaskBuffer;
 		StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
 		StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
+		SemaphoreHandle_t destroy_mutex;
 #endif
 #endif
 	} active_remote;
 	} active_remote;
 	void *owner;
 	void *owner;
@@ -93,6 +94,7 @@ extern log_level	raop_loglevel;
 static log_level 	*loglevel = &raop_loglevel;
 static log_level 	*loglevel = &raop_loglevel;
 
 
 static void*	rtsp_thread(void *arg);
 static void*	rtsp_thread(void *arg);
+static void		abort_rtsp(raop_ctx_t *ctx);
 static bool 	handle_rtsp(raop_ctx_t *ctx, int sock);
 static bool 	handle_rtsp(raop_ctx_t *ctx, int sock);
 
 
 static char*	rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
 static char*	rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
@@ -198,6 +200,12 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
 	return ctx;
 	return ctx;
 }
 }
 
 
+/*----------------------------------------------------------------------------*/
+void raop_abort(struct raop_ctx_s *ctx) {
+	LOG_INFO("[%p]: aborting RTSP session at next select() wakeup", ctx);
+	ctx->abort = true;
+}	
+
 /*----------------------------------------------------------------------------*/
 /*----------------------------------------------------------------------------*/
 void raop_delete(struct raop_ctx_s *ctx) {
 void raop_delete(struct raop_ctx_s *ctx) {
 #ifdef WIN32
 #ifdef WIN32
@@ -270,7 +278,7 @@ if (!ctx) return;
 }
 }
 
 
 /*----------------------------------------------------------------------------*/
 /*----------------------------------------------------------------------------*/
-void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
+bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
 	struct sockaddr_in addr;
 	struct sockaddr_in addr;
 	int sock;
 	int sock;
 	char *command = NULL;
 	char *command = NULL;
@@ -323,7 +331,7 @@ 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
 	// no command to send to remote or no remote found yet
 	if (!command || !ctx->active_remote.port) {
 	if (!command || !ctx->active_remote.port) {
 		NFREE(command);
 		NFREE(command);
-		return;
+		return false;
 	}
 	}
 
 
 	sock = socket(AF_INET, SOCK_STREAM, 0);
 	sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -354,6 +362,8 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
 
 
 	free(command);
 	free(command);
 	closesocket(sock);
 	closesocket(sock);
+	
+	return true;
 }
 }
 
 
 /*----------------------------------------------------------------------------*/
 /*----------------------------------------------------------------------------*/
@@ -373,6 +383,7 @@ static void *rtsp_thread(void *arg) {
 
 
 			sock = accept(ctx->sock, (struct sockaddr*) &peer, &addrlen);
 			sock = accept(ctx->sock, (struct sockaddr*) &peer, &addrlen);
 			ctx->peer.s_addr = peer.sin_addr.s_addr;
 			ctx->peer.s_addr = peer.sin_addr.s_addr;
+			ctx->abort = false;
 
 
 			if (sock != -1 && ctx->running) {
 			if (sock != -1 && ctx->running) {
 				LOG_INFO("got RTSP connection %u", sock);
 				LOG_INFO("got RTSP connection %u", sock);
@@ -383,12 +394,13 @@ static void *rtsp_thread(void *arg) {
 		FD_SET(sock, &rfds);
 		FD_SET(sock, &rfds);
 
 
 		n = select(sock + 1, &rfds, NULL, NULL, &timeout);
 		n = select(sock + 1, &rfds, NULL, NULL, &timeout);
-
-		if (!n) continue;
+		
+		if (!n && !ctx->abort) continue;
 
 
 		if (n > 0) res = handle_rtsp(ctx, sock);
 		if (n > 0) res = handle_rtsp(ctx, sock);
 
 
-		if (n < 0 || !res) {
+		if (n < 0 || !res || ctx->abort) {
+			abort_rtsp(ctx);
 			closesocket(sock);
 			closesocket(sock);
 			LOG_INFO("RTSP close %u", sock);
 			LOG_INFO("RTSP close %u", sock);
 			sock = -1;
 			sock = -1;
@@ -460,27 +472,6 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		NFREE(ctx->rtsp.aeskey);
 		NFREE(ctx->rtsp.aeskey);
 		NFREE(ctx->rtsp.aesiv);
 		NFREE(ctx->rtsp.aesiv);
 		NFREE(ctx->rtsp.fmtp);
 		NFREE(ctx->rtsp.fmtp);
-		
-		// LMS might has taken over the player, leaving us with a running RTP session (should not happen)
-		if (ctx->rtp) {
-			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) {
 		if ((p = strcasestr(body, "rsaaeskey")) != NULL) {
 			unsigned char *aeskey;
 			unsigned char *aeskey;
@@ -522,6 +513,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
 		pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
 #else
 #else
 		ctx->active_remote.running = true;
 		ctx->active_remote.running = true;
+		ctx->active_remote.destroy_mutex = xSemaphoreCreateMutex();		
 		ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
 		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);
 		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		
 #endif		
@@ -600,11 +592,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
 		ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
 		ctx->active_remote.running = false;
 		ctx->active_remote.running = false;
 
 
-		// task might not need to be resumed anyway
-		vTaskResume(ctx->active_remote.thread);
-		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+		xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
 		vTaskDelete(ctx->active_remote.thread);
 		vTaskDelete(ctx->active_remote.thread);
-
+		vSemaphoreDelete(ctx->active_remote.thread);
+		
 		heap_caps_free(ctx->active_remote.xTaskBuffer);
 		heap_caps_free(ctx->active_remote.xTaskBuffer);
 		
 		
 		LOG_INFO("[%p]: mDNS search task terminated", ctx);
 		LOG_INFO("[%p]: mDNS search task terminated", ctx);
@@ -681,6 +672,35 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 	return true;
 	return true;
 }
 }
 
 
+/*----------------------------------------------------------------------------*/
+void abort_rtsp(raop_ctx_t *ctx) {
+	// first stop RTP process
+	if (ctx->rtp) {
+		rtp_end(ctx->rtp);
+		ctx->rtp = NULL;
+		LOG_INFO("[%p]: RTP thread aborted", ctx);
+	}	
+
+	if (ctx->active_remote.running) {
+		// need to make sure no search is on-going and reclaim task memory
+		ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
+		ctx->active_remote.running = false;
+
+		xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
+		vTaskDelete(ctx->active_remote.thread);
+		vSemaphoreDelete(ctx->active_remote.thread);
+
+		heap_caps_free(ctx->active_remote.xTaskBuffer);
+		memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
+		
+		LOG_INFO("[%p]: Remote search thread aborted", ctx);
+	}	
+	
+	NFREE(ctx->rtsp.aeskey);
+	NFREE(ctx->rtsp.aesiv);
+	NFREE(ctx->rtsp.fmtp);
+}	
+
 /*----------------------------------------------------------------------------*/
 /*----------------------------------------------------------------------------*/
 #ifdef WIN32
 #ifdef WIN32
 bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
 bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
@@ -746,20 +766,10 @@ static void* search_remote(void *args) {
 		mdns_query_results_free(results);
 		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
+	// can't use xNotifyGive as it seems LWIP is using it as well
+	xSemaphoreGive(ctx->active_remote.destroy_mutex);
 	vTaskSuspend(NULL);
 	vTaskSuspend(NULL);
-
+	
 	return NULL;
 	return NULL;
  }
  }
 #endif
 #endif

+ 2 - 1
components/raop/raop.h

@@ -27,6 +27,7 @@
 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);
 							     raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
 void  		  raop_delete(struct raop_ctx_s *ctx);
 void  		  raop_delete(struct raop_ctx_s *ctx);
-void		  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param);
+void		  raop_abort(struct raop_ctx_s *ctx);
+bool		  raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param);
 
 
 #endif
 #endif

+ 2 - 1
components/raop/raop_sink.c

@@ -178,6 +178,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
 void raop_disconnect(void) {
 void raop_disconnect(void) {
 	LOG_INFO("forced disconnection");
 	LOG_INFO("forced disconnection");
 	displayer_control(DISPLAYER_SHUTDOWN);
 	displayer_control(DISPLAYER_SHUTDOWN);
-	raop_cmd(raop, RAOP_STOP, NULL);
+	// in case we can't communicate with AirPlay controller, abort session 
+	if (!raop_cmd(raop, RAOP_STOP, NULL)) raop_abort(raop);
 	actrls_unset();
 	actrls_unset();
 }
 }

+ 7 - 11
components/squeezelite/decode_external.c

@@ -46,7 +46,7 @@ static bool enable_airplay;
 #define SYNC_NB				5
 #define SYNC_NB				5
 
 
 static raop_event_t	raop_state;
 static raop_event_t	raop_state;
-static bool raop_expect_stop = false;
+
 static struct {
 static struct {
 	bool enabled, start;
 	bool enabled, start;
 	s32_t error[SYNC_NB];
 	s32_t error[SYNC_NB];
@@ -123,9 +123,8 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
 		LOG_INFO("BT sink started");
 		LOG_INFO("BT sink started");
 		break;
 		break;
 	case BT_SINK_AUDIO_STOPPED:	
 	case BT_SINK_AUDIO_STOPPED:	
-		// do we still need that?
 		if (output.external == DECODE_BT) {
 		if (output.external == DECODE_BT) {
-			output.state = OUTPUT_OFF;
+			if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
 			LOG_INFO("BT sink stopped");
 			LOG_INFO("BT sink stopped");
 		}	
 		}	
 		break;
 		break;
@@ -136,6 +135,7 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
 	case BT_SINK_STOP:		
 	case BT_SINK_STOP:		
 		_buf_flush(outputbuf);
 		_buf_flush(outputbuf);
 		output.state = OUTPUT_STOPPED;
 		output.state = OUTPUT_STOPPED;
+		output.stop_time = gettime_ms();
 		LOG_INFO("BT sink stopped");
 		LOG_INFO("BT sink stopped");
 		break;
 		break;
 	case BT_SINK_PAUSE:		
 	case BT_SINK_PAUSE:		
@@ -253,18 +253,14 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 			output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
 			output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
 			break;
 			break;
 		case RAOP_STOP:
 		case RAOP_STOP:
-			LOG_INFO("Stop", NULL);
-			output.state = OUTPUT_OFF;
-			output.frames_played = 0;
-			raop_state = event;
-			break;
 		case RAOP_FLUSH:
 		case RAOP_FLUSH:
-			LOG_INFO("Flush", NULL);
-			raop_expect_stop = true;
+			if (event == RAOP_FLUSH) { LOG_INFO("Flush", NULL); }
+			else { LOG_INFO("Stop", NULL); }
 			raop_state = event;
 			raop_state = event;
 			_buf_flush(outputbuf);		
 			_buf_flush(outputbuf);		
-			output.state = OUTPUT_STOPPED;
+			if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
 			output.frames_played = 0;
 			output.frames_played = 0;
+			output.stop_time = gettime_ms();
 			break;
 			break;
 		case RAOP_PLAY: {
 		case RAOP_PLAY: {
 			LOG_INFO("Play", NULL);
 			LOG_INFO("Play", NULL);

+ 88 - 46
components/squeezelite/display.c

@@ -84,7 +84,10 @@ extern struct outputstate output;
 static struct {
 static struct {
 	TaskHandle_t task;
 	TaskHandle_t task;
 	SemaphoreHandle_t mutex;
 	SemaphoreHandle_t mutex;
-} displayer;	
+	int width, height;
+	bool dirty;
+	bool owned;
+} displayer = { .dirty = true, .owned = true };	
 
 
 #define LONG_WAKE 		(10*1000)
 #define LONG_WAKE 		(10*1000)
 #define SB_HEIGHT		32
 #define SB_HEIGHT		32
@@ -147,26 +150,28 @@ static u8_t SETD_width;
 #define LINELEN				40
 #define LINELEN				40
 
 
 static log_level loglevel = lINFO;
 static log_level loglevel = lINFO;
+
 static bool (*slimp_handler_chain)(u8_t *data, int len);
 static bool (*slimp_handler_chain)(u8_t *data, int len);
 static void (*slimp_loop_chain)(void);
 static void (*slimp_loop_chain)(void);
 static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport);
 static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport);
-static int display_width, display_height;
-static bool display_dirty = true;
+static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd);
 
 
 #define max(a,b) (((a) > (b)) ? (a) : (b))
 #define max(a,b) (((a) > (b)) ? (a) : (b))
 
 
 static void server(in_addr_t ip, u16_t hport, u16_t cport);
 static void server(in_addr_t ip, u16_t hport, u16_t cport);
 static void send_server(void);
 static void send_server(void);
 static bool handler(u8_t *data, int len);
 static bool handler(u8_t *data, int len);
+static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd);
 static void vfdc_handler( u8_t *_data, int bytes_read);
 static void vfdc_handler( u8_t *_data, int bytes_read);
 static void grfe_handler( u8_t *data, int len);
 static void grfe_handler( u8_t *data, int len);
 static void grfb_handler(u8_t *data, int len);
 static void grfb_handler(u8_t *data, int len);
 static void grfs_handler(u8_t *data, int len);
 static void grfs_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
-static void visu_handler( u8_t *data, int len);
+static void visu_handler(u8_t *data, int len);
 
 
 static void displayer_task(void* arg);
 static void displayer_task(void* arg);
 
 
+
 /* scrolling undocumented information
 /* scrolling undocumented information
 	grfs	
 	grfs	
 		B: screen number
 		B: screen number
@@ -208,8 +213,8 @@ bool sb_display_init(void) {
 	}	
 	}	
 	
 	
 	// need to force height to 32 maximum
 	// need to force height to 32 maximum
-	display_width = display->width;
-	display_height = min(display->height, SB_HEIGHT);
+	displayer.width = display->width;
+	displayer.height = min(display->height, SB_HEIGHT);
 	SETD_width = display->width;
 	SETD_width = display->width;
 
 
 	// create visu configuration
 	// create visu configuration
@@ -223,10 +228,10 @@ bool sb_display_init(void) {
 	displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
 	displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
 	
 	
 	// size scroller
 	// size scroller
-	scroller.scroll.max = (display_width * display_height / 8) * 10;
+	scroller.scroll.max = (displayer.width * displayer.height / 8) * 10;
 	scroller.scroll.frame = malloc(scroller.scroll.max);
 	scroller.scroll.frame = malloc(scroller.scroll.max);
-	scroller.back.frame = malloc(display_width * display_height / 8);
-	scroller.frame = malloc(display_width * display_height / 8);
+	scroller.back.frame = malloc(displayer.width * displayer.height / 8);
+	scroller.frame = malloc(displayer.width * displayer.height / 8);
 
 
 	// chain handlers
 	// chain handlers
 	slimp_handler_chain = slimp_handler;
 	slimp_handler_chain = slimp_handler;
@@ -238,9 +243,39 @@ bool sb_display_init(void) {
 	notify_chain = server_notify;
 	notify_chain = server_notify;
 	server_notify = server;
 	server_notify = server;
 	
 	
+	display_bus_chain = display_bus;
+	display_bus = display_bus_handler;
+	
 	return true;
 	return true;
 }
 }
 
 
+/****************************************************************************************
+ * Receive display bus commands
+ */
+static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd) {
+	// don't answer to own requests
+	if (from == &displayer) return false ;
+	
+	LOG_INFO("Display bus command %d", cmd);
+	
+	xSemaphoreTake(displayer.mutex, portMAX_DELAY);
+	
+	switch (cmd) {
+	case DISPLAY_BUS_TAKE:
+		displayer.owned = false;
+		break;
+	case DISPLAY_BUS_GIVE:
+		displayer.owned = true;
+		break;
+	}
+	
+	xSemaphoreGive(displayer.mutex);
+	
+	if (display_bus_chain) return (*display_bus_chain)(from, cmd);
+	else return true;
+}
+
+
 /****************************************************************************************
 /****************************************************************************************
  * Send message to server (ANIC at that time)
  * Send message to server (ANIC at that time)
  */
  */
@@ -289,9 +324,9 @@ static void send_server(void) {
 static void server(in_addr_t ip, u16_t hport, u16_t cport) {
 static void server(in_addr_t ip, u16_t hport, u16_t cport) {
 	char msg[32];
 	char msg[32];
 	sprintf(msg, "%s:%hu", inet_ntoa(ip), hport);
 	sprintf(msg, "%s:%hu", inet_ntoa(ip), hport);
-	display->text(DISPLAY_FONT_DEFAULT, DISPLAY_CENTERED, DISPLAY_CLEAR | DISPLAY_UPDATE, msg);
+	if (displayer.owned) display->text(DISPLAY_FONT_DEFAULT, DISPLAY_CENTERED, DISPLAY_CLEAR | DISPLAY_UPDATE, msg);
 	SETD_width = display->width;
 	SETD_width = display->width;
-	display_dirty = true;
+	displayer.dirty = true;
 	if (notify_chain) (*notify_chain)(ip, hport, cport);
 	if (notify_chain) (*notify_chain)(ip, hport, cport);
 }
 }
 
 
@@ -301,24 +336,21 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) {
 static bool handler(u8_t *data, int len){
 static bool handler(u8_t *data, int len){
 	bool res = true;
 	bool res = true;
 	
 	
-	// don't do anything if we dont own the display (no lock needed)
-	if (!output.external || output.state < OUTPUT_STOPPED) {
-		if (!strncmp((char*) data, "vfdc", 4)) {
-			vfdc_handler(data, len);
-		} else if (!strncmp((char*) data, "grfe", 4)) {
-			grfe_handler(data, len);
-		} else if (!strncmp((char*) data, "grfb", 4)) {
-			grfb_handler(data, len);
-		} else if (!strncmp((char*) data, "grfs", 4)) {
-			grfs_handler(data, len);		
-		} else if (!strncmp((char*) data, "grfg", 4)) {
-			grfg_handler(data, len);
-		} else if (!strncmp((char*) data, "visu", 4)) {
-			visu_handler(data, len);
-		} else {
-			res = false;
-		}
-	}	
+	if (!strncmp((char*) data, "vfdc", 4)) {
+		vfdc_handler(data, len);
+	} else if (!strncmp((char*) data, "grfe", 4)) {
+		grfe_handler(data, len);
+	} else if (!strncmp((char*) data, "grfb", 4)) {
+		grfb_handler(data, len);
+	} else if (!strncmp((char*) data, "grfs", 4)) {
+		grfs_handler(data, len);		
+	} else if (!strncmp((char*) data, "grfg", 4)) {
+		grfg_handler(data, len);
+	} else if (!strncmp((char*) data, "visu", 4)) {
+		visu_handler(data, len);
+	} else {
+		res = false;
+	}
 	
 	
 	// chain protocol handlers (bitwise or is fine)
 	// chain protocol handlers (bitwise or is fine)
 	if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len);
 	if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len);
@@ -448,20 +480,23 @@ static void grfe_handler( u8_t *data, int len) {
 	
 	
 	scroller.active = false;
 	scroller.active = false;
 	
 	
-	// if we are displaying visu on a small screen, do not do screen update
+	// we are not in control or we are displaying visu on a small screen, do not do screen update
 	if (visu.mode && !visu.col && visu.row < SB_HEIGHT) {
 	if (visu.mode && !visu.col && visu.row < SB_HEIGHT) {
 		xSemaphoreGive(displayer.mutex);
 		xSemaphoreGive(displayer.mutex);
 		return;
 		return;
 	}	
 	}	
 	
 	
-	// did we have something that might have write on the bottom of a SB_HEIGHT+ display
-	if (display_dirty) {
-		display->clear(true);
-		display_dirty = false;
-	}	
+	if (displayer.owned) {
+		// did we have something that might have write on the bottom of a SB_HEIGHT+ display
+		if (displayer.dirty) {
+			display->clear(true);
+			displayer.dirty = false;
+		}	
 	
 	
-	display->draw_cbr(data + sizeof(struct grfe_packet), display_width, display_height);
-	display->update();
+		// draw new frame
+		display->draw_cbr(data + sizeof(struct grfe_packet), displayer.width, displayer.height);
+		display->update();
+	}	
 	
 	
 	xSemaphoreGive(displayer.mutex);
 	xSemaphoreGive(displayer.mutex);
 	
 	
@@ -518,7 +553,7 @@ static void grfs_handler(u8_t *data, int len) {
 		scroller.first = true;
 		scroller.first = true;
 		
 		
 		// background excludes space taken by visu (if any)
 		// background excludes space taken by visu (if any)
-		scroller.back.width = display_width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0);
+		scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0);
 
 
 		// set scroller steps & beginning
 		// set scroller steps & beginning
 		if (pkt->direction == 1) {
 		if (pkt->direction == 1) {
@@ -557,10 +592,14 @@ static void grfg_handler(u8_t *data, int len) {
 	memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
 	memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
 		
 		
 	// update display asynchronously (frames are oganized by columns)
 	// update display asynchronously (frames are oganized by columns)
-	memcpy(scroller.frame, scroller.back.frame, scroller.back.width * display_height / 8);
-	for (int i = 0; i < scroller.width * display_height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * display_height / 8 + i];
-	display->draw_cbr(scroller.frame, scroller.back.width, display_height);
-	display->update();
+	memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8);
+	for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i];
+	
+	// can only write if we really own display
+	if (displayer.owned) {
+		display->draw_cbr(scroller.frame, scroller.back.width, displayer.height);
+		display->update();
+	}	
 		
 		
 	// now we can active scrolling, but only if we are not on a small screen
 	// now we can active scrolling, but only if we are not on a small screen
 	if (!visu.mode || visu.col || visu.row >= SB_HEIGHT) scroller.active = true;
 	if (!visu.mode || visu.col || visu.row >= SB_HEIGHT) scroller.active = true;
@@ -578,6 +617,7 @@ static void grfg_handler(u8_t *data, int len) {
  * Update visualization bars
  * Update visualization bars
  */
  */
 static void visu_update(void) {
 static void visu_update(void) {
+	// no need to protect against no woning the display as we are playing	
 	if (pthread_mutex_trylock(&visu_export.mutex)) return;
 	if (pthread_mutex_trylock(&visu_export.mutex)) return;
 				
 				
 	// not enough samples
 	// not enough samples
@@ -731,6 +771,7 @@ static void visu_handler( u8_t *data, int len) {
 		// give up if not enough space
 		// give up if not enough space
 		if (visu.bar_width < 0)	{
 		if (visu.bar_width < 0)	{
 			visu.mode = VISU_BLANK;
 			visu.mode = VISU_BLANK;
+			LOG_WARN("Not enough room for displaying visu");
 		} else {
 		} else {
 			// de-activate scroller if we are taking main screen
 			// de-activate scroller if we are taking main screen
 			if (visu.row < SB_HEIGHT) scroller.active = false;
 			if (visu.row < SB_HEIGHT) scroller.active = false;
@@ -781,10 +822,10 @@ static void displayer_task(void *args) {
 			
 			
 			// do we have more to scroll (scroll.width is the last column from which we havea  full zone)
 			// do we have more to scroll (scroll.width is the last column from which we havea  full zone)
 			if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) {
 			if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) {
-				memcpy(scroller.frame, scroller.back.frame, scroller.back.width * display_height / 8);
-				for (int i = 0; i < scroller.width * display_height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * display_height / 8 + i];
+				memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8);
+				for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i];
 				scroller.scrolled += scroller.by;
 				scroller.scrolled += scroller.by;
-				display->draw_cbr(scroller.frame, scroller.width, display_height);	
+				if (displayer.owned) display->draw_cbr(scroller.frame, scroller.width, displayer.height);	
 				
 				
 				// short sleep & don't need background update
 				// short sleep & don't need background update
 				scroller.wake = scroller.speed;
 				scroller.wake = scroller.speed;
@@ -813,7 +854,8 @@ static void displayer_task(void *args) {
 			visu.wake = 100;
 			visu.wake = 100;
 		}
 		}
 		
 		
-		display->update();
+		// need to make sure we own display
+		if (displayer.owned) display->update();
 		
 		
 		// release semaphore and sleep what's needed
 		// release semaphore and sleep what's needed
 		xSemaphoreGive(displayer.mutex);
 		xSemaphoreGive(displayer.mutex);

+ 36 - 35
components/squeezelite/slimproto.c

@@ -690,46 +690,47 @@ static void slimproto_run() {
 			UNLOCK_D;
 			UNLOCK_D;
 			
 			
 			LOCK_O;
 			LOCK_O;
-			status.output_full = _buf_used(outputbuf);
-			status.output_size = outputbuf->size;
-			status.frames_played = output.frames_played_dmp;
-			status.current_sample_rate = output.current_sample_rate;
-			status.updated = output.updated;
-			status.device_frames = output.device_frames;
-						
-			if (output.track_started) {
-				_sendSTMs = true;
-				output.track_started = false;
-				status.stream_start = output.track_start_time;
-			}
+			if (!output.external) {
+				status.output_full = _buf_used(outputbuf);
+				status.output_size = outputbuf->size;
+				status.frames_played = output.frames_played_dmp;
+				status.current_sample_rate = output.current_sample_rate;
+				status.updated = output.updated;
+				status.device_frames = output.device_frames;
+									
+				if (output.track_started) {
+					_sendSTMs = true;
+					output.track_started = false;
+					status.stream_start = output.track_start_time;
+				}
 #if PORTAUDIO
 #if PORTAUDIO
-			if (output.pa_reopen) {
-				_pa_open();
-				output.pa_reopen = false;
-			}
+				if (output.pa_reopen) {
+					_pa_open();
+					output.pa_reopen = false;
+				}
 #endif
 #endif
-			if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
-				output.state = OUTPUT_BUFFER;
-			}
-			if (!output.external && output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
-				_decode_state == DECODE_STOPPED) {
-
-				_sendSTMu = true;
-				sentSTMu = true;
-				LOG_DEBUG("output underrun");
-				output.state = OUTPUT_STOPPED;
-				output.stop_time = now;
-			}
-			if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
-
-				_sendSTMo = true;
-				sentSTMo = true;
-			}
+				if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
+					output.state = OUTPUT_BUFFER;
+				}
+				if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
+					_decode_state == DECODE_STOPPED) {
+
+					_sendSTMu = true;
+					sentSTMu = true;
+					LOG_DEBUG("output underrun");
+					output.state = OUTPUT_STOPPED;
+					output.stop_time = now;
+				}
+				if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
+					_sendSTMo = true;
+					sentSTMo = true;
+				}
+			}	
 			if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) {
 			if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) {
 				output.state = OUTPUT_OFF;
 				output.state = OUTPUT_OFF;
 				LOG_DEBUG("output timeout");
 				LOG_DEBUG("output timeout");
-			}
-			if (!output.external && output.state == OUTPUT_RUNNING && now - status.last > 1000) {
+			}			
+			if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
 				_sendSTMt = true;
 				_sendSTMt = true;
 				status.last = now;
 				status.last = now;
 			}
 			}