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

pipeline_flash_tone例程在播放提示音时总会有POP音 (AUD-6512)

Open laodi-chen opened this issue 5 months ago • 4 comments

ESP-IDF:5.4.2 ESP-ADF:master 开发板:ESP32-Lyrat-Mini V1.2

使用的例程没有任何修改,flashTone分区烧录的例程目录下./tools/audio-esp.bin 在播放前或播放后总是会有一声POP音啪的一声,严重时会导致播放的第一个字完全听不见 应该如何处理这种情况?

laodi-chen avatar Jul 06 '25 10:07 laodi-chen

建议默认是一个 关PA状态, 开始播放的时候开 PA, 播放结束再关PA

shootao avatar Jul 07 '25 13:07 shootao

@shootao 在什么时机去做PA的开关比较合适?我这里尝试了但是没有怎么改善

// tone_player.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_element.h"
#include "tone_stream.h"
#include "mp3_decoder.h"
#include "i2s_stream.h"
#include "board.h"
#include "esp_log.h"
#include "es8311.h"

#include "tone_player.h"

#define TAG "TONE_PLAYER"
#define TONE_QUEUE_LEN 8

typedef struct
{
    tone_type_t tone;
    tone_cb_t callback;
    bool preemptive;
} tone_request_t;

const char *tone_uri[] = {
    "flash://tone/0_try.mp3",
};

static QueueHandle_t tone_queue = NULL;
static TaskHandle_t tone_task_handle = NULL;
static audio_pipeline_handle_t pipeline = NULL;
static audio_event_iface_handle_t evt = NULL;
static audio_element_handle_t tone_stream = NULL, mp3_decoder = NULL, i2s_stream = NULL;
static tone_request_t current_playing;
static bool is_playing = false;

static void stop_pipeline(void)
{
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_reset_ringbuffer(pipeline);
    audio_pipeline_terminate(pipeline);
}

static void prepare_and_start_playback(tone_request_t *req)
{
    audio_element_set_uri(tone_stream, tone_uri[req->tone]);
    audio_pipeline_reset_ringbuffer(pipeline);
    audio_pipeline_reset_elements(pipeline);
    audio_pipeline_run(pipeline);
    is_playing = true;
}

static void tone_play_task(void *arg)
{
    tone_request_t req;
    while (1)
    {
        if (!is_playing && xQueueReceive(tone_queue, &current_playing, portMAX_DELAY) == pdTRUE)
        {
            prepare_and_start_playback(&current_playing);
        }
        audio_event_iface_msg_t msg;
        if (audio_event_iface_listen(evt, &msg, 100 / portTICK_PERIOD_MS) == ESP_OK)
        {
            if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
                msg.source == (void *)mp3_decoder &&
                msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO)
            {

                audio_element_info_t music_info = {0};
                audio_element_getinfo(mp3_decoder, &music_info);
                ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
                         music_info.sample_rates, music_info.bits, music_info.channels);

                i2s_stream_set_clk(i2s_stream, music_info.sample_rates, music_info.bits, music_info.channels);
                // 播放前打开PA
                es8311_pa_power(true);
            }

            if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
                msg.source == (void *)i2s_stream &&
                msg.cmd == AEL_MSG_CMD_REPORT_STATUS &&
                ((int)msg.data == AEL_STATUS_STATE_STOPPED || (int)msg.data == AEL_STATUS_STATE_FINISHED))
            {
                // 播放完成先关PA再关管道
                es8311_pa_power(false);
                stop_pipeline();
                if (current_playing.callback)
                    current_playing.callback();
                is_playing = false;
                continue;
            }
        }

        while (xQueueReceive(tone_queue, &req, 0) == pdTRUE)
        {
            if (req.preemptive)
            {
                stop_pipeline();
                if (current_playing.callback)
                    current_playing.callback();
                current_playing = req;
                prepare_and_start_playback(&req);
                break;
            }
            else
            {
                xQueueSendToBack(tone_queue, &req, 0);
                break;
            }
        }
    }
}

esp_err_t tone_player_init(void)
{
    // 初始化时关闭PA
    es8311_pa_power(false);

    if (tone_queue)
        return ESP_OK; // Already initialized

    tone_queue = xQueueCreate(TONE_QUEUE_LEN, sizeof(tone_request_t));
    if (!tone_queue)
        return ESP_FAIL;

    audio_board_handle_t board_handle = audio_board_init();
    audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);

    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline = audio_pipeline_init(&pipeline_cfg);
    if (!pipeline)
        return ESP_FAIL;

    tone_stream_cfg_t tone_cfg = TONE_STREAM_CFG_DEFAULT();
    tone_cfg.type = AUDIO_STREAM_READER;
    tone_stream = tone_stream_init(&tone_cfg);

    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);

    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_stream = i2s_stream_init(&i2s_cfg);

    audio_pipeline_register(pipeline, tone_stream, "tone");
    audio_pipeline_register(pipeline, mp3_decoder, "mp3");
    audio_pipeline_register(pipeline, i2s_stream, "i2s");

    const char *link_tag[3] = {"tone", "mp3", "i2s"};
    audio_pipeline_link(pipeline, &link_tag[0], 3);

    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    evt = audio_event_iface_init(&evt_cfg);
    audio_pipeline_set_listener(pipeline, evt);

    xTaskCreate(tone_play_task, "tone_player_task", 4096, NULL, 5, &tone_task_handle);
    ESP_LOGI(TAG, "Tone player initialized");
    return ESP_OK;
}

esp_err_t play_tone(tone_type_t tone, tone_cb_t callback, bool preemptive)
{
    if (!tone_queue)
        return ESP_FAIL;
    if (tone >= TONE_TYPE_MAX)
        return ESP_ERR_INVALID_ARG;

    tone_request_t req = {
        .tone = tone,
        .callback = callback,
        .preemptive = preemptive};
    return xQueueSendToBack(tone_queue, &req, pdMS_TO_TICKS(100)) == pdTRUE ? ESP_OK : ESP_FAIL;
}


laodi-chen avatar Jul 13 '25 10:07 laodi-chen

@laodi-chen 这个具体的原因是由于 i2s set clk 的时候 i2s 驱动关了 DMA 导致的。以下有几个方案

  1. 让 app_main 的 task 优先级高于 i2s stream(i2s_stream 默认的23),确保I2S 的数据在 set clk 之后 write
  2. 使用 rsp 的方式, 不使用 set clk (Ref) 3.如果是提示音,尽量将所有的音频改为统一格式, 在 i2s init 的时候就配置好, 不需要调用 i2s set clk

shootao avatar Jul 16 '25 04:07 shootao

@laodi-chen 你提示音是什么采样率?

jason-mao avatar Aug 13 '25 04:08 jason-mao