浏览代码

optimize GPIO expander + external decoder fix

- external decoders sink callback had infinite loop when output buffer was full and would never empty
- race condition when playback stopped/restarted while waiting for output buffer to empty
Philippe G 3 年之前
父节点
当前提交
344730e1bc
共有 3 个文件被更改,包括 45 次插入16 次删除
  1. 21 2
      components/services/gpio_exp.c
  2. 1 1
      components/services/gpio_exp.h
  3. 23 13
      components/squeezelite/decode_external.c

+ 21 - 2
components/services/gpio_exp.c

@@ -29,6 +29,8 @@
  
 typedef struct gpio_exp_s {
 	uint32_t first, last;
+	int intr;
+	bool intr_pending;
 	struct  {
 		struct gpio_exp_phy_s phy;
 		spi_device_handle_t spi_handle;
@@ -176,6 +178,7 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 	n_expanders++;
 	expander->first = config->base;
 	expander->last = config->base + config->count - 1;
+	expander->intr = config->intr;
 	expander->mutex = xSemaphoreCreateMutex();
 
 	// create a task to handle asynchronous requests (only write at this time)
@@ -189,7 +192,7 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 	}
 
 	// set interrupt if possible
-	if (config->intr > 0) {
+	if (config->intr >= 0) {
 		gpio_pad_select_gpio(config->intr);
 		gpio_set_direction(config->intr, GPIO_MODE_INPUT);
 
@@ -402,12 +405,16 @@ esp_err_t gpio_isr_handler_remove_x(int gpio) {
  * INTR low-level handler
  */
 static void IRAM_ATTR intr_isr_handler(void* arg) {
+	gpio_exp_t *self = (gpio_exp_t*) arg;
 	BaseType_t woken = pdFALSE;
 	
+	// activate all, including ourselves
+	for (int i = 0; i < n_expanders; i++) if (expanders[i].intr == self->intr) expanders[i].intr_pending = true; 
+	
 	xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken);
 	if (woken) portYIELD_FROM_ISR();
 
-	ESP_EARLY_LOGD(TAG, "INTR for expander base %d", gpio_exp_get_base(arg));
+	ESP_EARLY_LOGD(TAG, "INTR for expander base %d", gpio_exp_get_base(self));
 }
 
 /****************************************************************************************
@@ -433,6 +440,18 @@ void service_handler(void *arg) {
 			   now, a loop will do */
 			for (int i = 0; i < n_expanders; i++) {
 				gpio_exp_t *expander = expanders + i;
+
+				// no interrupt for that gpio
+				if (expander->intr < 0) continue;
+
+				// only check expander with pending interrupts
+				gpio_intr_disable(expander->intr);
+				if (!expander->intr_pending) {
+					gpio_intr_enable(expander->intr);
+					continue;
+				}
+				expander->intr_pending = false;
+				gpio_intr_enable(expander->intr);
 				
 				xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50));
 

+ 1 - 1
components/services/gpio_exp.h

@@ -16,7 +16,7 @@ struct gpio_exp_s;
 
 typedef struct {
 	char model[32];
-	uint8_t intr;
+	int intr;
 	uint8_t count;
 	uint32_t base;
 	struct gpio_exp_phy_s {

+ 23 - 13
components/squeezelite/decode_external.c

@@ -37,6 +37,8 @@ static EXT_RAM_ATTR struct {
 } raop_sync;
 #endif
 
+static bool abort_sink ;
+
 #define LOCK_O   mutex_lock(outputbuf->mutex)
 #define UNLOCK_O mutex_unlock(outputbuf->mutex)
 #define LOCK_D   mutex_lock(decode.mutex);
@@ -63,11 +65,12 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
 		LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
 		return;
 	} 
-	
-	// there will always be room at some point
-	while (len) {
-		LOCK_O;
 
+	LOCK_O;
+	abort_sink = false;
+
+	// there will always be room at some point
+	while (len && wait && !abort_sink) {
 		bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
 		bytes = min(len, bytes);
 #if BYTES_PER_FRAME == 4
@@ -86,11 +89,16 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
 		len -= bytes;
 		data += bytes;
 				
-		UNLOCK_O;
-		
 		// allow i2s to empty the buffer if needed
-		if (len && !space && wait--) usleep(20000);
+		if (len && !space) {
+			wait--;
+			UNLOCK_O;
+			usleep(50000);
+			LOCK_O;
+		}
 	}	
+
+	UNLOCK_O;
 	
 	if (!wait) {
 		LOG_WARN("Waited too long, dropping frames");
@@ -105,7 +113,7 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
 {
 	// 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");
+		LOG_WARN("Cannot use BT sink while LMS/AirPlay are controlling player");
 		return false;
 	} 	
 
@@ -115,11 +123,11 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
 		
 	switch(cmd) {
 	case BT_SINK_AUDIO_STARTED:
+		_buf_flush(outputbuf);
 		output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
 		output.external = DECODE_BT;
 		output.state = OUTPUT_STOPPED;
 		output.frames_played = 0;
-		_buf_flush(outputbuf);
 		if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
 		LOG_INFO("BT sink started");
 		break;
@@ -132,17 +140,18 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
 		break;
 	case BT_SINK_PLAY:
 		output.state = OUTPUT_RUNNING;
-		LOG_INFO("BT playing");
+		LOG_INFO("BT play");
 		break;
 	case BT_SINK_STOP:		
 		_buf_flush(outputbuf);
 		output.state = OUTPUT_STOPPED;
 		output.stop_time = gettime_ms();
-		LOG_INFO("BT stopped");
+		abort_sink = true;
+		LOG_INFO("BT stop");
 		break;
 	case BT_SINK_PAUSE:		
 		output.stop_time = gettime_ms();
-		LOG_INFO("BT paused, just silence");
+		LOG_INFO("BT pause, just silence");
 		break;
 	case BT_SINK_RATE:
 		output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
@@ -184,7 +193,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 {
 	// 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");
+		LOG_WARN("Cannot use Airplay sink while LMS/BT are controlling player");
 		return false;
 	} 	
 
@@ -269,6 +278,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 			raop_state = event;
 			_buf_flush(outputbuf);		
 			if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
+			abort_sink = true;
 			output.frames_played = 0;
 			output.stop_time = gettime_ms();
 			break;