mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-10 21:47:04 +03:00
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:
@@ -29,6 +29,8 @@
|
|||||||
|
|
||||||
typedef struct gpio_exp_s {
|
typedef struct gpio_exp_s {
|
||||||
uint32_t first, last;
|
uint32_t first, last;
|
||||||
|
int intr;
|
||||||
|
bool intr_pending;
|
||||||
struct {
|
struct {
|
||||||
struct gpio_exp_phy_s phy;
|
struct gpio_exp_phy_s phy;
|
||||||
spi_device_handle_t spi_handle;
|
spi_device_handle_t spi_handle;
|
||||||
@@ -176,6 +178,7 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
|
|||||||
n_expanders++;
|
n_expanders++;
|
||||||
expander->first = config->base;
|
expander->first = config->base;
|
||||||
expander->last = config->base + config->count - 1;
|
expander->last = config->base + config->count - 1;
|
||||||
|
expander->intr = config->intr;
|
||||||
expander->mutex = xSemaphoreCreateMutex();
|
expander->mutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
// create a task to handle asynchronous requests (only write at this time)
|
// 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
|
// set interrupt if possible
|
||||||
if (config->intr > 0) {
|
if (config->intr >= 0) {
|
||||||
gpio_pad_select_gpio(config->intr);
|
gpio_pad_select_gpio(config->intr);
|
||||||
gpio_set_direction(config->intr, GPIO_MODE_INPUT);
|
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
|
* INTR low-level handler
|
||||||
*/
|
*/
|
||||||
static void IRAM_ATTR intr_isr_handler(void* arg) {
|
static void IRAM_ATTR intr_isr_handler(void* arg) {
|
||||||
|
gpio_exp_t *self = (gpio_exp_t*) arg;
|
||||||
BaseType_t woken = pdFALSE;
|
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);
|
xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken);
|
||||||
if (woken) portYIELD_FROM_ISR();
|
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 */
|
now, a loop will do */
|
||||||
for (int i = 0; i < n_expanders; i++) {
|
for (int i = 0; i < n_expanders; i++) {
|
||||||
gpio_exp_t *expander = 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));
|
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50));
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct gpio_exp_s;
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char model[32];
|
char model[32];
|
||||||
uint8_t intr;
|
int intr;
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
uint32_t base;
|
uint32_t base;
|
||||||
struct gpio_exp_phy_s {
|
struct gpio_exp_phy_s {
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ static EXT_RAM_ATTR struct {
|
|||||||
} raop_sync;
|
} raop_sync;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static bool abort_sink ;
|
||||||
|
|
||||||
#define LOCK_O mutex_lock(outputbuf->mutex)
|
#define LOCK_O mutex_lock(outputbuf->mutex)
|
||||||
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
|
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
|
||||||
#define LOCK_D mutex_lock(decode.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");
|
LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
|
||||||
return;
|
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(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
|
||||||
bytes = min(len, bytes);
|
bytes = min(len, bytes);
|
||||||
#if BYTES_PER_FRAME == 4
|
#if BYTES_PER_FRAME == 4
|
||||||
@@ -86,11 +89,16 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
|
|||||||
len -= bytes;
|
len -= bytes;
|
||||||
data += bytes;
|
data += bytes;
|
||||||
|
|
||||||
UNLOCK_O;
|
|
||||||
|
|
||||||
// allow i2s to empty the buffer if needed
|
// 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) {
|
if (!wait) {
|
||||||
LOG_WARN("Waited too long, dropping frames");
|
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
|
// 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) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +123,11 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
|||||||
|
|
||||||
switch(cmd) {
|
switch(cmd) {
|
||||||
case BT_SINK_AUDIO_STARTED:
|
case BT_SINK_AUDIO_STARTED:
|
||||||
|
_buf_flush(outputbuf);
|
||||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
||||||
output.external = DECODE_BT;
|
output.external = DECODE_BT;
|
||||||
output.state = OUTPUT_STOPPED;
|
output.state = OUTPUT_STOPPED;
|
||||||
output.frames_played = 0;
|
output.frames_played = 0;
|
||||||
_buf_flush(outputbuf);
|
|
||||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||||
LOG_INFO("BT sink started");
|
LOG_INFO("BT sink started");
|
||||||
break;
|
break;
|
||||||
@@ -132,17 +140,18 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
|||||||
break;
|
break;
|
||||||
case BT_SINK_PLAY:
|
case BT_SINK_PLAY:
|
||||||
output.state = OUTPUT_RUNNING;
|
output.state = OUTPUT_RUNNING;
|
||||||
LOG_INFO("BT playing");
|
LOG_INFO("BT play");
|
||||||
break;
|
break;
|
||||||
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();
|
output.stop_time = gettime_ms();
|
||||||
LOG_INFO("BT stopped");
|
abort_sink = true;
|
||||||
|
LOG_INFO("BT stop");
|
||||||
break;
|
break;
|
||||||
case BT_SINK_PAUSE:
|
case BT_SINK_PAUSE:
|
||||||
output.stop_time = gettime_ms();
|
output.stop_time = gettime_ms();
|
||||||
LOG_INFO("BT paused, just silence");
|
LOG_INFO("BT pause, just silence");
|
||||||
break;
|
break;
|
||||||
case BT_SINK_RATE:
|
case BT_SINK_RATE:
|
||||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
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
|
// 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) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +278,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
|||||||
raop_state = event;
|
raop_state = event;
|
||||||
_buf_flush(outputbuf);
|
_buf_flush(outputbuf);
|
||||||
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
|
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
|
||||||
|
abort_sink = true;
|
||||||
output.frames_played = 0;
|
output.frames_played = 0;
|
||||||
output.stop_time = gettime_ms();
|
output.stop_time = gettime_ms();
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user