esp-adf icon indicating copy to clipboard operation
esp-adf copied to clipboard

Audio_event_iface_listen blocks indefinitely after audio_pipeline_set_listener is called during dynamic pipeline reconfiguration (AUD-7020)

Open Jack-Agilian opened this issue 3 weeks ago • 0 comments

ESP-ADF Issue Report: audio_event_iface_listen blocks indefinitely after audio_pipeline_set_listener is called during dynamic pipeline reconfiguration

Environment

  • ESP-ADF version: [Your version, e.g., v2.6]
  • ESP-IDF version: v5.4
  • Development Kit: [Your board, e.g., ESP32-S3 custom board]
  • Operating System: Linux

Problem Description

When dynamically reconfiguring an audio pipeline (unlink → relink → set_listener), a task blocked on audio_event_iface_listen() with portMAX_DELAY will never receive events again, even after audio_pipeline_set_listener() is called.

Expected Behavior

After calling audio_pipeline_set_listener(), a task waiting on audio_event_iface_listen() should be able to receive new events from the reconfigured pipeline.

Actual Behavior

The task remains blocked indefinitely and never receives any events, including events sent via audio_event_iface_sendout().

Root Cause Analysis

After examining the source code in audio_event_iface.c, I found the following issue:

1. audio_event_iface_listen() waits on a queue_set

esp_err_t audio_event_iface_read(audio_event_iface_handle_t evt, audio_event_iface_msg_t *msg, TickType_t wait_time)
{
    if (evt->queue_set) {
        QueueSetMemberHandle_t active_queue;
        active_queue = xQueueSelectFromSet(evt->queue_set, wait_time);  // Blocks here
        // ...
    }
    return ESP_FAIL;
}

2. audio_event_iface_update_listener() destroys and recreates queue_set

This function is called internally by audio_event_iface_set_listener():

static esp_err_t audio_event_iface_update_listener(audio_event_iface_handle_t listen)
{
    // ...
    if (listen->queue_set) {
        vQueueDelete(listen->queue_set);  // Old queue_set is deleted!
        listen->queue_set = NULL;
    }
    // ...
    listen->queue_set = xQueueCreateSet(queue_size);  // New queue_set is created
    // ...
}

3. The Problem

When audio_pipeline_set_listener() is called:

  1. The task is blocked on xQueueSelectFromSet(OLD_queue_set, portMAX_DELAY)
  2. audio_event_iface_update_listener() deletes the OLD queue_set and creates a NEW one
  3. The task is still waiting on the deleted queue_set
  4. The task will never wake up because the queue_set it's waiting on no longer exists

Reproduction Steps

// Task waiting for events
static void event_task(void *pvParameters) {
    audio_event_iface_handle_t evt = (audio_event_iface_handle_t)pvParameters;
    while (1) {
        audio_event_iface_msg_t msg;
        // This will block indefinitely after pipeline reconfiguration
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret == ESP_OK) {
            // Process event
        }
    }
}

// In another task or function:
void reconfigure_pipeline() {
    // Step 1: Unlink pipeline (this calls audio_pipeline_remove_listener internally)
    audio_pipeline_unlink(pipeline);
    
    // Step 2: Relink with different elements
    audio_pipeline_link(pipeline, new_link_tags, 4);
    
    // Step 3: Set listener again
    audio_pipeline_set_listener(pipeline, evt);  // This triggers queue_set recreation
    
    // Step 4: Send an event
    audio_event_iface_sendout(evt, &msg);  // This event will NEVER be received!
    
    // Step 5: Run the pipeline
    audio_pipeline_run(pipeline);  // Pipeline runs, but events are not received
}

Workaround

Use a finite timeout instead of portMAX_DELAY:

// This works because the task will timeout and retry with the new queue_set
esp_err_t ret = audio_event_iface_listen(evt, &msg, pdMS_TO_TICKS(500));

Suggested Fix

Option 1: Before deleting the old queue_set, send a wake-up signal to any waiting tasks.

Option 2: Add a mechanism to notify listeners before reconfiguration, allowing them to safely exit the wait.

Option 3: Document this limitation clearly in the API documentation, recommending users to use finite timeouts when dynamic pipeline reconfiguration is expected.

Additional Context

This issue was discovered when implementing a music player that dynamically switches between different audio sources (e.g., embedded flash tones and SD card music). The pipeline needs to be unlinked and relinked to change the source element, which triggers this bug.


Labels suggestion: bug, audio_pipeline, audio_event_iface

Jack-Agilian avatar Dec 11 '25 05:12 Jack-Agilian