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
This commit is contained in:
Philippe G
2021-12-10 23:03:13 -08:00
parent af7c181063
commit 344730e1bc
3 changed files with 45 additions and 16 deletions

View File

@@ -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));
}
/****************************************************************************************
@@ -434,6 +441,18 @@ void service_handler(void *arg) {
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));
// read GPIOs and clear all pending status

View File

@@ -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 {

View File

@@ -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);
@@ -64,10 +66,11 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
return;
}
// there will always be room at some point
while (len) {
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;