语音控制SD卡播放无法正常恢复播放

James York
Posts: 2
Joined: Mon Jun 23, 2025 11:57 am

语音控制SD卡播放无法正常恢复播放

Postby James York » Mon Jun 23, 2025 12:52 pm

语音控制SD卡播放时,一旦使用“上一曲”或“下一曲”切换音频文件,暂停后则无法恢复sd播放。
追踪原因,是由于: sd 管道中的 rsp 元素 发生延迟 导致。
操作过程如下:
唤醒 --- 下一曲 (切换到下一曲并开始播放)--- 唤醒 (暂停sd, 等待命令词)--- 没有命令词发生,定时器超时,恢复sd播放 ,无法恢复播放。

以下是输出信息:

Code: Select all

Build fst from commands.
I (4709) wwe_tone_sd_v04_1: Starting Recording Pipeline
D (4709) AUDIO_PIPELINE: start el[         rec_i2s], linked:1, state:1,[0x3c10d5ac], 
I (4711) AUDIO_ELEMENT: [rec_i2s-0x3c10d5ac] Element task created
D (4717) AUDIO_PIPELINE: start el[      rec_filter], linked:1, state:1,[0x3c10d910], 
I (4724) AUDIO_ELEMENT: [rec_filter-0x3c10d910] Element task created
D (4730) AUDIO_PIPELINE: start el[         rec_raw], linked:1, state:1,[0x3c10da4c], 
D (4738) AUDIO_ELEMENT: REPORT_STATUS,[rec_raw]evt out cmd = 8,status:12
I (4744) AUDIO_ELEMENT: [rec_raw-0x3c10da4c] Element task created
I (4750) AUDIO_PIPELINE: Func:audio_pipeline_run, Line:408, MEM Total:4569792 Bytes, Inter:189275 Bytes, Dram:189275 Bytes, Dram largest free:90100Bytes

D (4764) AUDIO_PIPELINE: resume,linked:1, state:1,[rec_i2s-0x3c10d5ac]
I (4770) AUDIO_ELEMENT: [rec_i2s] AEL_MSG_CMD_RESUME,state:1
D (4775) AUDIO_ELEMENT: [rec_i2s] el opened
D (4779) AUDIO_ELEMENT: REPORT_STATUS,[rec_i2s]evt out cmd = 8,status:12
D (4786) AUDIO_PIPELINE: resume,linked:1, state:1,[rec_filter-0x3c10d910]
I (4792) AUDIO_ELEMENT: [rec_filter] AEL_MSG_CMD_RESUME,state:1
I (4799) RSP_FILTER: sample rate of source data : 48000, channel of source data : 4, sample rate of destination data : 16000, channel of destination data : 4
D (4812) AUDIO_ELEMENT: [rec_filter] el opened
D (4816) AUDIO_ELEMENT: REPORT_STATUS,[rec_filter]evt out cmd = 8,status:12
D (4827) AUDIO_PIPELINE: resume,linked:1, state:3,[rec_raw-0x3c10da4c]
D (4829) AUDIO_ELEMENT: REPORT_STATUS,[rec_raw]evt out cmd = 8,status:12
D (4836) AUDIO_ELEMENT: [rec_raw] RESUME: Element is already running, state:3, task_run:1, is_running:1
I (4844) AUDIO_PIPELINE: Pipeline started
I (4869) wwe_tone_sd_v04_1: Creating voice command timer...
I (4870) wwe_tone_sd_v04_1: [AppMain] g_app_event_queue created successfully.
I (4871) wwe_tone_sd_v04_1: Func:app_main, Line:511, MEM Total:4565280 Bytes, Inter:186907 Bytes, Dram:186907 Bytes, Dram largest free:90100Bytes

I (4884) wwe_tone_sd_v04_1: [AppMain] app_event_handler_task created.
I (4891) wwe_tone_sd_v04_1: Func:app_main, Line:516, MEM Total:4548032 Bytes, Inter:169659 Bytes, Dram:169659 Bytes, Dram largest free:90100Bytes

I (4903) wwe_tone_sd_v04_1: [AppMain] control_logic_task created.
I (4909) wwe_tone_sd_v04_1: System Initialized. Waiting for voice commands...
I (4949) wwe_tone_sd_v04_1: Func:app_main, Line:522, MEM Total:4530784 Bytes, Inter:152411 Bytes, Dram:152411 Bytes, Dram largest free:71668Bytes

I (4954) wwe_tone_sd_v04_1: Starting main event loop...
D (14621) [* REC_CB *]: WAKEUP_START.
D (14621) [* REC_CB *]: Sending App Event 1 (APP_EVENT_VOICE_WAKEUP_START) to g_app_event_queue.
D (14622) [* CTRL_TASK *]: WAKEUP_START.
D (14625) [* REC_CB *]: App Event 1 (APP_EVENT_VOICE_WAKEUP_START) sent successfully.
D (14844) [* REC_CB *]: VAD_START.
D (15962) [* REC_CB *]: VAD_END.
D (16562) [* REC_CB *]: WAKEUP_END.
D (16562) [* REC_CB *]: Sending App Event 2 (APP_EVENT_VOICE_WAKEUP_END) to g_app_event_queue.
D (16563) [* CTRL_TASK *]: WAKEUP_END.
D (16566) [* CTRL_TASK *]: Timer is started.
D (16570) [* REC_CB *]: App Event 2 (APP_EVENT_VOICE_WAKEUP_END) sent successfully.
D (17323) [* REC_CB *]: AUDIO_REC_COMMAND_DECT: command_id 3, phrase_id 3, prob 0.449510, str:  xia ju
D (17324) [* REC_CB *]: Sending App Event 3 (APP_EVENT_VOICE_CMD_RECOGNIZED) to g_app_event_queue.
D (17331) [* CTRL_TASK *]: Timer is stopped by COMMAND_DECT.
D (17336) AUDIO_PIPELINE: audio_element_stop
W (17340) AUDIO_PIPELINE: Without stop, st:1
W (17344) AUDIO_PIPELINE: Without wait stop, st:1
D (17348) AUDIO_PIPELINE: Destroy audio_pipeline elements
W (17353) AUDIO_ELEMENT: [sd_file] Element has not create when AUDIO_ELEMENT_TERMINATE
W (17361) AUDIO_ELEMENT: [sd_mp3] Element has not create when AUDIO_ELEMENT_TERMINATE
W (17369) AUDIO_ELEMENT: [sd_filter] Element has not create when AUDIO_ELEMENT_TERMINATE
W (17376) AUDIO_ELEMENT: [sd_i2s] Element has not create when AUDIO_ELEMENT_TERMINATE
D (17384) [* CTRL_TASK *]: URL: file://sdcard/podcast.mp3
D (17389) AUDIO_PIPELINE: start el[         sd_file], linked:1, state:1,[0x3c108754], 
D (17397) [* REC_CB *]: App Event 3 (APP_EVENT_VOICE_CMD_RECOGNIZED) sent successfully.
I (17430) AUDIO_ELEMENT: [sd_file-0x3c108754] Element task created
D (17430) AUDIO_PIPELINE: start el[          sd_mp3], linked:1, state:1,[0x3c1088f0], 
I (17433) AUDIO_ELEMENT: [sd_mp3-0x3c1088f0] Element task created
D (17438) AUDIO_PIPELINE: start el[       sd_filter], linked:1, state:1,[0x3c108a74], 
I (17446) AUDIO_ELEMENT: [sd_filter-0x3c108a74] Element task created
D (17452) AUDIO_PIPELINE: start el[          sd_i2s], linked:1, state:1,[0x3c108d44], 
I (17460) AUDIO_ELEMENT: [sd_i2s-0x3c108d44] Element task created
I (17465) AUDIO_PIPELINE: Func:audio_pipeline_run, Line:408, MEM Total:4502776 Bytes, Inter:142895 Bytes, Dram:142895 Bytes, Dram largest free:71668Bytes

D (17479) AUDIO_PIPELINE: resume,linked:1, state:1,[sd_file-0x3c108754]
I (17526) AUDIO_ELEMENT: [sd_file] AEL_MSG_CMD_RESUME,state:1
D (17526) AUDIO_PIPELINE: resume,linked:1, state:1,[sd_mp3-0x3c1088f0]
I (17527) AUDIO_ELEMENT: [sd_mp3] AEL_MSG_CMD_RESUME,state:1
D (17532) AUDIO_PIPELINE: resume,linked:1, state:1,[sd_filter-0x3c108a74]
I (17539) AUDIO_ELEMENT: [sd_filter] AEL_MSG_CMD_RESUME,state:1
D (17544) AUDIO_PIPELINE: resume,linked:1, state:1,[sd_i2s-0x3c108d44]
I (17550) AUDIO_ELEMENT: [sd_i2s] AEL_MSG_CMD_RESUME,state:1
D (17556) AUDIO_ELEMENT: [sd_i2s] el opened
D (17560) AUDIO_ELEMENT: REPORT_STATUS,[sd_i2s]evt out cmd = 8,status:12
I (17566) AUDIO_PIPELINE: Pipeline started
D (17566) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=sd_stream_writer (at 0x3c108d44), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
D (17570) [* CTRL_TASK *]: pipeline_sd is ran by next-cmd.
D (17592) [* CTRL_TASK *]: Command is done. cur_cmd = SD_CONTROL_NONE.
I (17540) MP3_DECODER: MP3 opened
I (17701) RSP_FILTER: sample rate of source data : 44100, channel of source data : 2, sample rate of destination data : 48000, channel of destination data : 2
D (17705) AUDIO_ELEMENT: [sd_filter] el opened
D (17709) AUDIO_ELEMENT: REPORT_STATUS,[sd_filter]evt out cmd = 8,status:12
D (17715) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_filter' (at 0x3c108a74), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
I (17746) FATFS_STREAM: File size: 25181759 byte, file position: 0
D (17747) AUDIO_ELEMENT: [sd_file] el opened
D (17747) AUDIO_ELEMENT: REPORT_STATUS,[sd_file]evt out cmd = 8,status:12
D (17752) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_file' (at 0x3c108754), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
I (17850) CODEC_ELEMENT_HELPER: The element is 0x3c1088f0. The reserve data 2 is 0x0.
D (17852) AUDIO_ELEMENT: [sd_mp3] el opened
D (17852) AUDIO_ELEMENT: REPORT_STATUS,[sd_mp3]evt out cmd = 8,status:12
D (17858) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_mp3' (at 0x3c1088f0), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
D (17883) AUDIO_ELEMENT: REPORT_INFO,[sd_mp3]evt out cmd:9,
D (17883) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_MUSIC_INFO(9), src_type=131072, src=Element 'sd_mp3' (at 0x3c1088f0), data_len=0, data_ptr=0x0 (MusicInfo)
D (17896) [* AUD_EVT *]: SD DECODER: Music info: sr=44100, bits=16, ch=2
D (36823) [* REC_CB *]: WAKEUP_START.
D (36823) [* REC_CB *]: Sending App Event 1 (APP_EVENT_VOICE_WAKEUP_START) to g_app_event_queue.
D (36824) [* REC_CB *]: App Event 1 (APP_EVENT_VOICE_WAKEUP_START) sent successfully.
D (36824) [* CTRL_TASK *]: WAKEUP_START.
D (36835) AUDIO_PIPELINE: pause [sd_file]  0x3c108754
I (36963) AUDIO_ELEMENT: [sd_file] ON_CMD: Processing AEL_MSG_CMD_PAUSE. Current state: 3
D (36965) AUDIO_ELEMENT: [sd_file] ON_CMD_PAUSE: Calling audio_element_process_deinit (el->close).
D (36969) AUDIO_ELEMENT: [sd_file] ON_CMD_PAUSE: audio_element_process_deinit (el->close) returned.
D (37057) [* REC_CB *]: VAD_START.
D (37062) AUDIO_ELEMENT: REPORT_STATUS,[sd_file]evt out cmd = 8,status:13
D (37078) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_file' (at 0x3c108754), data_len=4, status_data=AEL_STATUS_STATE_PAUSED(13)
I (37150) AUDIO_ELEMENT: [sd_file] AEL_MSG_CMD_PAUSE
D (37151) AUDIO_PIPELINE: pause [sd_mp3]  0x3c1088f0
I (37157) AUDIO_ELEMENT: [sd_mp3] ON_CMD: Processing AEL_MSG_CMD_PAUSE. Current state: 3
D (37157) AUDIO_ELEMENT: [sd_mp3] ON_CMD_PAUSE: Calling audio_element_process_deinit (el->close).
I (37168) MP3_DECODER: Closed
D (37170) AUDIO_ELEMENT: [sd_mp3] ON_CMD_PAUSE: audio_element_process_deinit (el->close) returned.
D (37178) AUDIO_ELEMENT: REPORT_STATUS,[sd_mp3]evt out cmd = 8,status:13
D (37185) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_mp3' (at 0x3c1088f0), data_len=4, status_data=AEL_STATUS_STATE_PAUSED(13)
I (37202) AUDIO_ELEMENT: [sd_mp3] AEL_MSG_CMD_PAUSE
D (37205) AUDIO_PIPELINE: pause [sd_filter]  0x3c108a74
D (37909) [* REC_CB *]: VAD_END.
D (38509) [* REC_CB *]: WAKEUP_END.
D (38509) [* REC_CB *]: Sending App Event 2 (APP_EVENT_VOICE_WAKEUP_END) to g_app_event_queue.
D (38510) [* REC_CB *]: App Event 2 (APP_EVENT_VOICE_WAKEUP_END) sent successfully.
D (39210) AUDIO_PIPELINE: pause [sd_i2s]  0x3c108d44
I (39239) AUDIO_ELEMENT: [sd_i2s] ON_CMD: Processing AEL_MSG_CMD_PAUSE. Current state: 3
D (39239) AUDIO_ELEMENT: [sd_i2s] ON_CMD_PAUSE: Calling audio_element_process_deinit (el->close).
D (39244) AUDIO_ELEMENT: [sd_i2s] ON_CMD_PAUSE: audio_element_process_deinit (el->close) returned.
D (39253) AUDIO_ELEMENT: REPORT_STATUS,[sd_i2s]evt out cmd = 8,status:13
I (39259) AUDIO_ELEMENT: [sd_i2s] AEL_MSG_CMD_PAUSE
D (39259) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=sd_stream_writer (at 0x3c108d44), data_len=4, status_data=AEL_STATUS_STATE_PAUSED(13)
D (39264) [* CTRL_TASK *]: SD_CONTROL_PLAY is pending by tone-1.
D (39286) [* CTRL_TASK *]: WAKEUP_END.
D (39290) [* CTRL_TASK *]: Timer is started.
I (43284) RECORDER_SR: MN dect quit
D (44290) [* TMR_CB *]: LATE_CMD_TIMEOUT event sent to queue.
D (44290) [* CTRL_TASK *]: LATE_CMD_TIMEOUT. isToneCmd: 0, isTonePending: 1
D (44291) [* CTRL_TASK *]: Timer expired. el_state is: 4.
D (44296) AUDIO_PIPELINE: resume,linked:1, state:4,[sd_file-0x3c108754]
I (44338) AUDIO_ELEMENT: [sd_file] AEL_MSG_CMD_RESUME,state:4
D (44338) AUDIO_PIPELINE: resume,linked:1, state:4,[sd_mp3-0x3c1088f0]
I (44339) AUDIO_ELEMENT: [sd_mp3] AEL_MSG_CMD_RESUME,state:4
D (44344) AUDIO_PIPELINE: resume,linked:1, state:3,[sd_filter-0x3c108a74]
D (44351) AUDIO_ELEMENT: REPORT_STATUS,[sd_filter]evt out cmd = 8,status:12
D (44357) AUDIO_ELEMENT: [sd_filter] RESUME: Element is already running, state:3, task_run:1, is_running:1
D (44357) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_filter' (at 0x3c108a74), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
D (44367) AUDIO_PIPELINE: resume,linked:1, state:4,[sd_i2s-0x3c108d44]
I (44390) AUDIO_ELEMENT: [sd_i2s] AEL_MSG_CMD_RESUME,state:4
D (44395) AUDIO_ELEMENT: [sd_i2s] el opened
D (44399) AUDIO_ELEMENT: REPORT_STATUS,[sd_i2s]evt out cmd = 8,status:12
D (44406) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=sd_stream_writer (at 0x3c108d44), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
D (44406) [* CTRL_TASK *]: Timer expired. SD-PLAYING is resumed.
I (44367) MP3_DECODER: MP3 opened
I (44441) FATFS_STREAM: File size: 25181759 byte, file position: 466944
D (44442) AUDIO_ELEMENT: [sd_file] el opened
D (44443) AUDIO_ELEMENT: REPORT_STATUS,[sd_file]evt out cmd = 8,status:12
D (44481) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_file' (at 0x3c108754), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
D (44498) AUDIO_ELEMENT: REPORT_INFO,[sd_mp3]evt out cmd:9,
D (44498) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_MUSIC_INFO(9), src_type=131072, src=Element 'sd_mp3' (at 0x3c1088f0), data_len=0, data_ptr=0x0 (MusicInfo)
D (44511) [* AUD_EVT *]: SD DECODER: Music info: sr=44100, bits=16, ch=2
D (44519) AUDIO_ELEMENT: [sd_mp3] el opened
D (44525) AUDIO_ELEMENT: REPORT_STATUS,[sd_mp3]evt out cmd = 8,status:12
D (44528) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_mp3' (at 0x3c1088f0), data_len=4, status_data=AEL_STATUS_STATE_RUNNING(12)
I (44554) AUDIO_ELEMENT: [sd_filter] ON_CMD: Processing AEL_MSG_CMD_PAUSE. Current state: 3
D (44554) AUDIO_ELEMENT: [sd_filter] ON_CMD_PAUSE: Calling audio_element_process_deinit (el->close).
D (44564) AUDIO_ELEMENT: [sd_filter] ON_CMD_PAUSE: audio_element_process_deinit (el->close) returned.
D (44572) AUDIO_ELEMENT: REPORT_STATUS,[sd_filter]evt out cmd = 8,status:13
D (44578) [* AUD_EVT *]: audio_evt_iface_listener: cmd=AEL_MSG_CMD_REPORT_STATUS(8), src_type=131072, src=Element 'sd_filter' (at 0x3c108a74), data_len=4, status_data=AEL_STATUS_STATE_PAUSED(13)
I (44596) AUDIO_ELEMENT: [sd_filter] AEL_MSG_CMD_PAUSE
以下是应用程序源代码:

Code: Select all

/**/
#include <stdio.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"

#include "esp_log.h"
#include "esp_err.h"

#include "audio_element.h"
#include "audio_idf_version.h"
#include "audio_mem.h"
#include "audio_pipeline.h"
#include "audio_recorder.h"
#include "audio_thread.h"
#include "audio_error.h"

//#include "audio_tone_uri.h"
#include "board.h"
#include "i2s_stream.h"
#include "mp3_decoder.h"
#include "filter_resample.h"
#include "raw_stream.h"
#include "recorder_sr.h"
//#include "tone_stream.h"
#include "es7210.h"
#include "model_path.h" 

#include "esp_peripherals.h"
#include "audio_event_iface.h"
#include "periph_sdcard.h"
#include "sdcard_list.h"
#include "sdcard_scan.h"
#include "fatfs_stream.h"


static char *TAG = "wwe_tone_sd_v04_1";   // 设置为 信息模式(ESP_LOG_INFO),与 调试模式 分离

// 设置为 调试模式(ESP_LOG_DEBUG):白色,容易观察
static char *TAG_REC_CB = "[* REC_CB *]";          // set:info, use:dbg
static char *TAG_TMR_CB = "[* TMR_CB *]";          // set:info, use:dbg
static char *TAG_AUD_EVT = "[* AUD_EVT *]";        // set:info, use:dbg
static char *TAG_CTRL_TASK = "[* CTRL_TASK *]";    // set:dbg, use:dbg

#define DEBUG_AUD_EVT   1   // 调试开关: 输出:音频管道 中的所有事件

// SD Card Playback Pipeline
audio_pipeline_handle_t pipeline_sd = NULL;
audio_element_handle_t sd_stream_reader = NULL, sd_mp3_decoder = NULL, filter_sd = NULL, sd_stream_writer = NULL;
playlist_operator_handle_t sdcard_list_handle = NULL; // To store scanned MP3 files


// Recording Pipeline
audio_pipeline_handle_t pipeline_rec = NULL;
audio_element_handle_t rec_stream_reader = NULL, filter_rec = NULL, raw_read = NULL;

audio_rec_handle_t recorder = NULL;
audio_event_iface_handle_t evt = NULL; // Unified event listener

// ----------------------------------------
// 简化命令词
//#define SPEECH_COMMANDS_V01 ("PLd;PeZ;PRmVmcS;NfKST;VnLYoM cP;VnLYoM DtN;") // 播放;暂停;上一曲;下一曲;大声点;小声点
//#define SPEECH_COMMANDS_V01 ("play;pause;previous;next;volume up;volume down;") // 播放;暂停;上一曲;下一曲;大声点;小声点
#define SPEECH_COMMANDS_V01 ("bo fang;zan ting;shang ju;xia ju;da sheng dian;xiao sheng dian;") // 播放;暂停;上一曲;下一曲;大声点;小声点
#define MAX_SINGLE_COMMAND_PHRASE_LENGTH 32

#define VOLUME_STEP 10

// 命令词ID (与SPEECH_COMMANDS_V01中的顺序对应)
#define CMD_ID_PLAY     0
#define CMD_ID_PAUSE    1
#define CMD_ID_PREV     2
#define CMD_ID_NEXT     3
#define CMD_ID_VOLUP    4
#define CMD_ID_VOLDN    5

typedef enum {
    SD_CONTROL_NONE = -1,
    SD_CONTROL_PLAY,
    SD_CONTROL_PAUSE,
    SD_CONTROL_PREV,
    SD_CONTROL_NEXT,
    SD_CONTROL_VOLUP,
    SD_CONTROL_VOLDN
} sd_state_e;

// Timer for late command
static TimerHandle_t timer_voice_cmd = NULL;
const uint32_t TIMEOUT_MS_CMD = 5000;   // 必须覆盖:命令词识别 窗口期,否则会造成:提示音2 与 SD恢复后的冲突。

// ----------------------------------------

// --- Application Event System ---
typedef enum {
    APP_EVENT_NONE = 0,
    APP_EVENT_VOICE_WAKEUP_START,    
    APP_EVENT_VOICE_WAKEUP_END,
    APP_EVENT_VOICE_CMD_RECOGNIZED,
    //APP_EVENT_TONE_PLAYBACK_FINISHED,
    APP_EVENT_SD_PLAYBACK_FINISHED,
    APP_EVENT_LATE_CMD_TIMEOUT
} app_event_type_t;

typedef struct {
    app_event_type_t type;
    int phrase_id;    
} app_event_t;

#define APP_EVENT_QUEUE_LENGTH 64
static QueueHandle_t g_app_event_queue = NULL;

// --- end of event ---------

static void app_event_handler_task(void *pvParameters); // 音频事件监听 及 消息发送
static void control_logic_task(void *pvParameters);     // 中控逻辑处理
static const char* app_event_type_to_string(app_event_type_t type);

// Task handles
static TaskHandle_t g_app_event_handler_task_handle = NULL;
static TaskHandle_t g_control_logic_task_handle = NULL;



static esp_err_t rec_engine_cb(audio_rec_evt_t *event, void *user_data)
{
    app_event_t app_evt;
    memset(&app_evt, 0, sizeof(app_event_t));   // 每次调用:回调函数 时都会 初始化 结构体变量:app_evt

    if (AUDIO_REC_WAKEUP_START == event->type) {
        ESP_LOGD(TAG_REC_CB, "WAKEUP_START.");
        app_evt.type = APP_EVENT_VOICE_WAKEUP_START;
    } 
    else if (AUDIO_REC_VAD_START == event->type) {
        ESP_LOGD(TAG_REC_CB, "VAD_START.");
    } 
    else if (AUDIO_REC_VAD_END == event->type) {
        ESP_LOGD(TAG_REC_CB, "VAD_END.");
    } 
    else if (AUDIO_REC_WAKEUP_END == event->type) {
        ESP_LOGD(TAG_REC_CB, "WAKEUP_END.");
        app_evt.type = APP_EVENT_VOICE_WAKEUP_END;
    } 
    else if (AUDIO_REC_COMMAND_DECT <= event->type) {
        recorder_sr_mn_result_t *mn_result = event->event_data;
        ESP_LOGD(TAG_REC_CB, "AUDIO_REC_COMMAND_DECT: command_id %d, phrase_id %d, prob %f, str: %s",
                 event->type, mn_result->phrase_id, mn_result->prob, mn_result->str);
        app_evt.type = APP_EVENT_VOICE_CMD_RECOGNIZED;
        app_evt.phrase_id = mn_result->phrase_id;
    } 
    else {
        ESP_LOGE(TAG_REC_CB, "Unknown recorder event: %d", event->type);
    }

    if (app_evt.type != APP_EVENT_NONE) {
        if (g_app_event_queue) {
            ESP_LOGD(TAG_REC_CB, "Sending App Event %d (%s) to g_app_event_queue.", app_evt.type, app_event_type_to_string(app_evt.type));
            if (xQueueSend(g_app_event_queue, &app_evt, pdMS_TO_TICKS(50)) != pdPASS) {
                ESP_LOGE(TAG_REC_CB, "Failed to send App Event %d to g_app_event_queue!", app_evt.type);
            } else {
                ESP_LOGD(TAG_REC_CB, "App Event %d (%s) sent successfully.", app_evt.type, app_event_type_to_string(app_evt.type));
            }
        } else {
            ESP_LOGE(TAG_REC_CB, "g_app_event_queue is NULL! Cannot send event %d.", app_evt.type);
        }
    }

    return ESP_OK;
}

// =======================================================================================
// Timer callback function
// =======================================================================================
static void timer_cb(TimerHandle_t xTimer) {    
    app_event_t evt;
    memset(&evt, 0, sizeof(app_event_t)); // Good practice to zero out

    evt.type = APP_EVENT_LATE_CMD_TIMEOUT;
    if (g_app_event_queue) {
        if (xQueueSend(g_app_event_queue, &evt, 0) != pdPASS) {
            ESP_LOGE(TAG_TMR_CB, "Failed to send LATE_CMD_TIMEOUT event to queue!");
        } else {
            ESP_LOGD(TAG_TMR_CB, "LATE_CMD_TIMEOUT event sent to queue.");
        }
    } else {
        ESP_LOGE(TAG_TMR_CB, "g_app_event_queue is NULL, cannot send LATE_CMD_TIMEOUT.");
    }    
}

static int input_cb_for_afe(int16_t *buffer, int buf_sz, void *user_ctx, TickType_t ticks)
{
    return raw_stream_read(raw_read, (char *)buffer, buf_sz);
}

void sdcard_url_save_cb(void *user_data, char *url)
{
    playlist_operator_handle_t pl_handle = (playlist_operator_handle_t)user_data;
    esp_err_t ret = sdcard_list_save(pl_handle, url);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to save sdcard url to playlist: %s", url);
    } else {
        ESP_LOGI(TAG, "Saved URL to playlist: %s", url);
    }
}

// Helper to convert app_event_type_t to string for logging
static const char* app_event_type_to_string(app_event_type_t type) {
    switch (type) {
        case APP_EVENT_NONE: return "APP_EVENT_NONE";
        case APP_EVENT_VOICE_WAKEUP_START: return "APP_EVENT_VOICE_WAKEUP_START";        
        case APP_EVENT_VOICE_WAKEUP_END: return "APP_EVENT_VOICE_WAKEUP_END";
        case APP_EVENT_VOICE_CMD_RECOGNIZED: return "APP_EVENT_VOICE_CMD_RECOGNIZED";
        //case APP_EVENT_TONE_PLAYBACK_FINISHED: return "APP_EVENT_TONE_PLAYBACK_FINISHED";
        case APP_EVENT_SD_PLAYBACK_FINISHED: return "APP_EVENT_SD_PLAYBACK_FINISHED";
        default: return "UNKNOWN_APP_EVENT";
    }
}
//  1:H --- ... ---           6:L
// none, err, wrn, info, dbg, vbe
static void log_clear(void)
{
    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set("AUDIO_THREAD", ESP_LOG_ERROR);
    esp_log_level_set("AUDIO_MEM", ESP_LOG_DEBUG); // Show mem usage
    // Add other noisy tags if needed
    esp_log_level_set("AUDIO_ELEMENT", ESP_LOG_DEBUG);
    esp_log_level_set("AUDIO_PIPELINE", ESP_LOG_DEBUG);

    esp_log_level_set("MP3_DECODER", ESP_LOG_DEBUG);
    esp_log_level_set("I2S_STREAM", ESP_LOG_DEBUG);
    esp_log_level_set("RSP_FILTER", ESP_LOG_DEBUG);

    esp_log_level_set(TAG, ESP_LOG_INFO);               // Set our tag to DEBUG for more info
    esp_log_level_set(TAG_REC_CB, ESP_LOG_DEBUG);        // set:info, use:dbg
    esp_log_level_set(TAG_TMR_CB, ESP_LOG_DEBUG);        // set:info, use:dbg
    esp_log_level_set(TAG_AUD_EVT, ESP_LOG_DEBUG);       // set:info, use:dbg
    esp_log_level_set(TAG_CTRL_TASK, ESP_LOG_DEBUG);    // set:info, use:dbg
}

//  for debug ----------------
#if (DEBUG_AUD_EVT)
// 辅助函数:将 audio_element_msg_cmd_t 转换为字符串
const char *get_ael_msg_cmd_str(audio_element_msg_cmd_t cmd) {
    switch (cmd) {
        case AEL_MSG_CMD_NONE: return "AEL_MSG_CMD_NONE";
        case AEL_MSG_CMD_FINISH: return "AEL_MSG_CMD_FINISH";
        case AEL_MSG_CMD_STOP: return "AEL_MSG_CMD_STOP";
        case AEL_MSG_CMD_PAUSE: return "AEL_MSG_CMD_PAUSE";
        case AEL_MSG_CMD_RESUME: return "AEL_MSG_CMD_RESUME";
        case AEL_MSG_CMD_DESTROY: return "AEL_MSG_CMD_DESTROY";
        case AEL_MSG_CMD_REPORT_STATUS: return "AEL_MSG_CMD_REPORT_STATUS";
        case AEL_MSG_CMD_REPORT_MUSIC_INFO: return "AEL_MSG_CMD_REPORT_MUSIC_INFO";
        case AEL_MSG_CMD_REPORT_CODEC_FMT: return "AEL_MSG_CMD_REPORT_CODEC_FMT";
        case AEL_MSG_CMD_REPORT_POSITION: return "AEL_MSG_CMD_REPORT_POSITION";
        default: return "UNKNOWN_CMD";
    }
}

// 辅助函数:将 audio_element_status_t 转换为字符串
const char *get_ael_status_str(audio_element_status_t status) {
    switch (status) {
        case AEL_STATUS_NONE: return "AEL_STATUS_NONE";
        case AEL_STATUS_ERROR_OPEN: return "AEL_STATUS_ERROR_OPEN";
        case AEL_STATUS_ERROR_INPUT: return "AEL_STATUS_ERROR_INPUT";
        case AEL_STATUS_ERROR_PROCESS: return "AEL_STATUS_ERROR_PROCESS";
        case AEL_STATUS_ERROR_OUTPUT: return "AEL_STATUS_ERROR_OUTPUT";
        case AEL_STATUS_ERROR_CLOSE: return "AEL_STATUS_ERROR_CLOSE";
        case AEL_STATUS_ERROR_TIMEOUT: return "AEL_STATUS_ERROR_TIMEOUT";
        case AEL_STATUS_ERROR_UNKNOWN: return "AEL_STATUS_ERROR_UNKNOWN";
        case AEL_STATUS_INPUT_DONE: return "AEL_STATUS_INPUT_DONE";
        case AEL_STATUS_INPUT_BUFFERING: return "AEL_STATUS_INPUT_BUFFERING";
        case AEL_STATUS_OUTPUT_DONE: return "AEL_STATUS_OUTPUT_DONE";
        case AEL_STATUS_OUTPUT_BUFFERING: return "AEL_STATUS_OUTPUT_BUFFERING";
        case AEL_STATUS_STATE_RUNNING: return "AEL_STATUS_STATE_RUNNING";
        case AEL_STATUS_STATE_PAUSED: return "AEL_STATUS_STATE_PAUSED";
        case AEL_STATUS_STATE_STOPPED: return "AEL_STATUS_STATE_STOPPED";
        case AEL_STATUS_STATE_FINISHED: return "AEL_STATUS_STATE_FINISHED";
        case AEL_STATUS_MOUNTED: return "AEL_STATUS_MOUNTED";
        case AEL_STATUS_UNMOUNTED: return "AEL_STATUS_UNMOUNTED";
        default: return "UNKNOWN_STATUS";
    }
}

// 辅助函数:获取 msg.source 的描述性字符串, 需要传入已知的 element handle 以便比较
const char *get_source_description(void *source_ptr,
            audio_element_handle_t known_sd_stream_writer) 
{
    static char description[128];

    if (source_ptr == NULL) {
        snprintf(description, sizeof(description), "NULL");
    } else if (source_ptr == (void *)known_sd_stream_writer) { // 新增对 sd_stream_writer 的判断
        snprintf(description, sizeof(description), "sd_stream_writer (at %p)", source_ptr);
    }
    else {
        char *tag = audio_element_get_tag((audio_element_handle_t)source_ptr);
        if (tag && strlen(tag) > 0) {
            snprintf(description, sizeof(description), "Element '%s' (at %p)", tag, source_ptr);
        } else {
            snprintf(description, sizeof(description), "Unknown Source (at %p)", source_ptr);
        }
    }
    return description;
}
#endif
// end of debug --------------


void app_main(void)
{
#if 1
    log_clear();
    ESP_LOGI(TAG, "Starting wwe_tone_sd_v01 application");

    audio_board_handle_t board_handle = audio_board_init();
    audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);
    audio_hal_set_volume(board_handle->audio_hal, 70); // Set a moderate volume

    // -------- Initialize Peripherals for SD card --------
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
    audio_board_sdcard_init(set, SD_MODE_1_LINE); // Initialize SD card peripheral

    // Create playlist and scan for MP3 files
    sdcard_list_create(&sdcard_list_handle);
    // Scan only for "mp3" extension in the root directory "/sdcard"
    sdcard_scan(sdcard_url_save_cb, "/sdcard", 0, (const char *[]){"mp3"}, 1, sdcard_list_handle);
    ESP_LOGI(TAG, "SD card scan complete. Playlist content:");
    sdcard_list_show(sdcard_list_handle); // Log the found files

    // -------- Unified Event Listener --------
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();  // 关键-1:大量音频事件 会造成拥堵
    evt_cfg.internal_queue_size = 15; // 或更大
    evt_cfg.external_queue_size = 15; // 或更大
    evt_cfg.queue_set_size = 30;      // 至少是 internal + external 之和
    evt = audio_event_iface_init(&evt_cfg);
    AUDIO_NULL_CHECK(TAG, evt, return);

    // -------- SD Card Playback Pipeline (pipeline_sd) --------
    ESP_LOGI(TAG, "Initializing SD Card Playback Pipeline (pipeline_sd)");
    audio_pipeline_cfg_t pipeline_sd_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline_sd = audio_pipeline_init(&pipeline_sd_cfg);
    AUDIO_NULL_CHECK(TAG, pipeline_sd, return);

    fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
    fatfs_cfg.type = AUDIO_STREAM_READER;
    sd_stream_reader = fatfs_stream_init(&fatfs_cfg);
    AUDIO_NULL_CHECK(TAG, sd_stream_reader, return);
    // URI will be set before playing

    mp3_decoder_cfg_t mp3_sd_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_sd_cfg.task_stack = 10*1024;    // test
    mp3_sd_cfg.out_rb_size = 6*1024;
    sd_mp3_decoder = mp3_decoder_init(&mp3_sd_cfg);
    AUDIO_NULL_CHECK(TAG, sd_mp3_decoder, return);

    rsp_filter_cfg_t rsp_sd_cfg = DEFAULT_RESAMPLE_FILTER_CONFIG();
    // Source info will be set dynamically from MP3 decoder

    //rsp_sd_cfg.task_prio = 10;//test

    rsp_sd_cfg.dest_rate = 48000;
    rsp_sd_cfg.dest_ch = 2;
    rsp_sd_cfg.dest_bits = 16;
    rsp_sd_cfg.mode = RESAMPLE_DECODE_MODE;
    rsp_sd_cfg.type = ESP_RESAMPLE_TYPE_AUTO;
    filter_sd = rsp_filter_init(&rsp_sd_cfg);
    AUDIO_NULL_CHECK(TAG, filter_sd, return);

    i2s_stream_cfg_t i2s_sd_cfg = I2S_STREAM_CFG_DEFAULT_WITH_PARA(I2S_NUM_0, 48000, 32, AUDIO_STREAM_WRITER);
    i2s_sd_cfg.expand_src_bits = 16; // Resampler outputs 16-bit, I2S DAC might need 32-bit samples (actual data 16-bit)
    i2s_sd_cfg.need_expand = true;
    sd_stream_writer = i2s_stream_init(&i2s_sd_cfg);
    AUDIO_NULL_CHECK(TAG, sd_stream_writer, return);

    audio_pipeline_register(pipeline_sd, sd_stream_reader, "sd_file");
    audio_pipeline_register(pipeline_sd, sd_mp3_decoder,   "sd_mp3");
    audio_pipeline_register(pipeline_sd, filter_sd,        "sd_filter");
    audio_pipeline_register(pipeline_sd, sd_stream_writer, "sd_i2s");

    const char *link_sd_tags[] = {"sd_file", "sd_mp3", "sd_filter", "sd_i2s"};
    audio_pipeline_link(pipeline_sd, &link_sd_tags[0], 4);
    audio_pipeline_set_listener(pipeline_sd, evt); // Use unified event listener

    
    // -------- Recording Pipeline (pipeline_rec) --------
    ESP_LOGI(TAG, "Initializing Recording Pipeline (pipeline_rec)");
    audio_pipeline_cfg_t pipeline_rec_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline_rec = audio_pipeline_init(&pipeline_rec_cfg);
    AUDIO_NULL_CHECK(TAG, pipeline_rec, return);

    i2s_stream_cfg_t i2s_rec_cfg = I2S_STREAM_CFG_DEFAULT_WITH_PARA(I2S_NUM_0, 48000, 32, AUDIO_STREAM_READER);
    rec_stream_reader = i2s_stream_init(&i2s_rec_cfg);
    AUDIO_NULL_CHECK(TAG, rec_stream_reader, return);

    rsp_filter_cfg_t rsp_rec_cfg = DEFAULT_RESAMPLE_FILTER_CONFIG();
    rsp_rec_cfg.src_rate = 48000;
    rsp_rec_cfg.dest_rate = 16000;
    rsp_rec_cfg.mode = RESAMPLE_UNCROSS_MODE;
    rsp_rec_cfg.src_ch = 4;
    rsp_rec_cfg.dest_ch = 4;
    rsp_rec_cfg.max_indata_bytes = 1024;
    filter_rec = rsp_filter_init(&rsp_rec_cfg);
    AUDIO_NULL_CHECK(TAG, filter_rec, return);

    raw_stream_cfg_t raw_cfg = RAW_STREAM_CFG_DEFAULT();
    raw_cfg.type = AUDIO_STREAM_READER;
    raw_read = raw_stream_init(&raw_cfg);
    AUDIO_NULL_CHECK(TAG, raw_read, return);

    audio_pipeline_register(pipeline_rec, rec_stream_reader, "rec_i2s");
    audio_pipeline_register(pipeline_rec, filter_rec,        "rec_filter");
    audio_pipeline_register(pipeline_rec, raw_read,          "rec_raw");

    const char *link_rec_tags[] = {"rec_i2s", "rec_filter", "rec_raw"};
    audio_pipeline_link(pipeline_rec, &link_rec_tags[0], 3);
    // No listener for rec pipeline, events come via recorder_sr callback

    // -------- Speech Recognition Engine --------
    ESP_LOGI(TAG, "Initializing Speech Recognition Engine");
    recorder_sr_cfg_t recorder_sr_cfg = DEFAULT_RECORDER_SR_CFG(
        AUDIO_ADC_INPUT_CH_FORMAT,
        "model",
        AFE_TYPE_SR,
        AFE_MODE_HIGH_PERF//AFE_MODE_LOW_COST
    );
    recorder_sr_cfg.afe_cfg->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
    recorder_sr_cfg.afe_cfg->wakenet_init = true;    
    recorder_sr_cfg.afe_cfg->vad_mode = VAD_MODE_0; // Normal mode
    recorder_sr_cfg.multinet_init = true;
    recorder_sr_cfg.mn_language = ESP_MN_CHINESE;
    recorder_sr_cfg.afe_cfg->aec_init = true;
    recorder_sr_cfg.afe_cfg->agc_mode = AFE_MN_PEAK_AGC_MODE_3;

    audio_rec_cfg_t rec_create_cfg = AUDIO_RECORDER_DEFAULT_CFG();
    rec_create_cfg.read = (recorder_data_read_t)&input_cb_for_afe;
    rec_create_cfg.sr_handle = recorder_sr_create(&recorder_sr_cfg, &rec_create_cfg.sr_iface);
    AUDIO_NULL_CHECK(TAG, rec_create_cfg.sr_handle, return);

    char err_msg[MAX_SINGLE_COMMAND_PHRASE_LENGTH];
    esp_err_t ret_sr_cmd = recorder_sr_reset_speech_cmd(rec_create_cfg.sr_handle, SPEECH_COMMANDS_V01, err_msg);
    if (ret_sr_cmd != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set speech commands: %s", err_msg);
    }
    rec_create_cfg.event_cb = rec_engine_cb;
    rec_create_cfg.wakeup_time = 2000; // 唤醒维持时间(唤醒~VAD触发之前)
    rec_create_cfg.wakeup_end = 600;   // vad_off 之后的静默时间
    rec_create_cfg.vad_start = 160;    // 有效的 连续活动语音 判断时长
    rec_create_cfg.vad_off = 300;      // 静默超时时间
    recorder = audio_recorder_create(&rec_create_cfg);
    AUDIO_NULL_CHECK(TAG, recorder, return);

    // -------- Start Recording Pipeline --------
    ESP_LOGI(TAG, "Starting Recording Pipeline");
    audio_pipeline_run(pipeline_rec); // Start capturing audio for SR

    // -------- Timer for voice command ------------
    ESP_LOGI(TAG, "Creating voice command timer...");
    timer_voice_cmd = xTimerCreate("VoiceCmdTmr",
                                    pdMS_TO_TICKS(TIMEOUT_MS_CMD),
                                    pdFALSE, // One-shot timer
                                    (void *)0, 
                                    timer_cb);
    if (timer_voice_cmd == NULL) {
        ESP_LOGE(TAG, "FATAL: Failed to create timer_voice_cmd! Halting.");
        return; 
    }

    g_app_event_queue = xQueueCreate(APP_EVENT_QUEUE_LENGTH, sizeof(app_event_t));
    if (g_app_event_queue == NULL) { ESP_LOGE(TAG, "[AppMain] FATAL: Failed to create g_app_event_queue! Halting."); return; }
    ESP_LOGI(TAG, "[AppMain] g_app_event_queue created successfully.");

    BaseType_t task_ret;    // 关键-2:任务堆栈需要足够大,以容纳音频事件的积累,但需要测试:10KB 是否合适。消息生产者,优先级 小于 消息消费者,但必须比其他音频管道事件高。
    AUDIO_MEM_SHOW(TAG);
    task_ret = xTaskCreate(app_event_handler_task, "app_evt_hdlr", 1024*16, NULL, configMAX_PRIORITIES - 11, &g_app_event_handler_task_handle);
    if (task_ret != pdPASS) { ESP_LOGE(TAG, "[AppMain] Failed to create app_event_handler_task!"); return; }
    ESP_LOGI(TAG, "[AppMain] app_event_handler_task created.");

    AUDIO_MEM_SHOW(TAG);
    task_ret = xTaskCreate(control_logic_task, "ctrl_logic", 1024*16, NULL, configMAX_PRIORITIES - 10, &g_control_logic_task_handle);
    if (task_ret != pdPASS) { ESP_LOGE(TAG, "[AppMain] Failed to create control_logic_task!"); return; }
    ESP_LOGI(TAG, "[AppMain] control_logic_task created.");

    ESP_LOGI(TAG, "System Initialized. Waiting for voice commands...");
    AUDIO_MEM_SHOW(TAG);


    // -------- Main Event Loop --------
    ESP_LOGI(TAG, "Starting main event loop...");

    while (1) {
        vTaskDelay(pdMS_TO_TICKS(30000));
    } // End while(1)

    // Cleanup (though this loop is infinite in this example)
    ESP_LOGI(TAG, "Stopping pipelines and deinitializing...");
    // ... (add deinit code if you plan to exit app_main)
#endif
}

static void app_event_handler_task(void *pvParameters) 
{
    if (!g_app_event_queue || !evt) {
        ESP_LOGE(TAG_AUD_EVT, "Critical component (app_queue or shared_listener) is NULL! Exiting.");
        g_app_event_handler_task_handle = NULL;
        vTaskDelete(NULL);
        return;
    }
    audio_event_iface_msg_t msg;
    while (1) {
        esp_err_t ret_listen = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret_listen != ESP_OK) {
            ESP_LOGE(TAG_AUD_EVT, "audio_evt_iface_listener error: %d", ret_listen);
            //vTaskDelay(pdMS_TO_TICKS(100));
            continue;
        }
        //AUDIO_MEM_SHOW(TAG);    // test
        // for debug ----------
        #if (DEBUG_AUD_EVT)
        const char *cmd_str = get_ael_msg_cmd_str(msg.cmd);
        const char *source_str = get_source_description(msg.source, sd_stream_writer); // 传入已知的 tone_stream_writer 句柄
        // 根据 msg.cmd 决定如何解释 msg.data
        if (msg.cmd == AEL_MSG_CMD_REPORT_STATUS) {
            const char *status_str = get_ael_status_str((audio_element_status_t)(int)msg.data);
            ESP_LOGD(TAG_AUD_EVT, "audio_evt_iface_listener: cmd=%s(%d), src_type=%d, src=%s, data_len=%d, status_data=%s(%d)",
                        cmd_str, msg.cmd, msg.source_type, source_str, msg.data_len, status_str, (int)msg.data);
        } else if (msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
            // 如果是音乐信息,msg.data 指向 audio_element_info_t
            // 你可以进一步解析并打印,但这里只做通用处理
            // 注意:如果 msg.need_free_data 为 true,则 msg.data 指向的内存需要被释放
            // audio_element_info_t* info = (audio_element_info_t*)msg.data;
            ESP_LOGD(TAG_AUD_EVT, "audio_evt_iface_listener: cmd=%s(%d), src_type=%d, src=%s, data_len=%d, data_ptr=%p (MusicInfo)",
                        cmd_str, msg.cmd, msg.source_type, source_str, msg.data_len, msg.data);
        } else {
            // 其他命令的通用日志
            ESP_LOGD(TAG_AUD_EVT, "audio_evt_iface_listener: cmd=%s(%d), src_type=%d, src=%s, data_len=%d, data_ptr=%p",
                        cmd_str, msg.cmd, msg.source_type, source_str, msg.data_len, msg.data);
        }
        #endif
        // end of debug -------

        app_event_t app_evt_to_send;
        memset(&app_evt_to_send, 0, sizeof(app_event_t));
        bool should_send_app_event = false;

        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT) {
            if (msg.cmd == AEL_MSG_CMD_REPORT_STATUS){
                if (msg.source == (void *)sd_stream_writer) {
                    if ((int)msg.data == AEL_STATUS_STATE_FINISHED) {
                        ESP_LOGD(TAG_AUD_EVT, "sd stream writer FINISHED.");
                        app_evt_to_send.type = APP_EVENT_SD_PLAYBACK_FINISHED; 
                        should_send_app_event = true;
                    }
                }
            }
            else if (msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {                
                if (msg.source == (void *)sd_mp3_decoder) {
                    // 直接处理,不再发送消息:(1) 仅仅使用json,(2)尽可能快地设置:filter_sd
                    audio_element_info_t music_info = {0};
                    audio_element_getinfo(sd_mp3_decoder, &music_info);
                    ESP_LOGD(TAG_AUD_EVT, "SD DECODER: Music info: sr=%d, bits=%d, ch=%d",
                            music_info.sample_rates, music_info.bits, music_info.channels);

                    if (filter_sd) { // filter_sd should be valid if pipeline_sd is up
                        esp_err_t chg_ret = rsp_filter_change_src_info(filter_sd,
                                                                    music_info.sample_rates,
                                                                    music_info.channels,
                                                                    music_info.bits);
                        if (chg_ret != ESP_OK) {
                            ESP_LOGE(TAG_AUD_EVT, "Failed to change sd-resampler src info.");
                        }
                    }                    
                }
            }
        }
        if (should_send_app_event) {        // pdMS_TO_TICKS(100)
            if (xQueueSend(g_app_event_queue, &app_evt_to_send, 0) != pdPASS) {
                ESP_LOGE(TAG_AUD_EVT, "[AppEvtHdlrTask] Failed to send App Event %d to g_app_event_queue!", app_evt_to_send.type);
            } else {
                ESP_LOGD(TAG_AUD_EVT, "[AppEvtHdlrTask] App Event %d (%s) sent successfully.",
                            app_evt_to_send.type, app_event_type_to_string(app_evt_to_send.type));
            }
        }
    }
}

static void control_logic_task(void *pvParameters) 
{    
    app_event_t app_event;
    
    bool isTurnStart = false;       // 一轮语音交互 是否 已经完成
    bool isTonePending = false;     // 提示音1 播放,挂起 正在播放的 SD 
    bool isToneCmd = false;         // 提示音2 播放, 也表明:命令已经识别
    sd_state_e sd_state = SD_CONTROL_NONE;    // SD状态 记录
    //sd_state_e cur_cmd = SD_CONTROL_NONE;     // 当前命令 记录

    while(1) {
        if (g_app_event_queue && xQueueReceive(g_app_event_queue, &app_event, portMAX_DELAY) == pdPASS) {
            //AUDIO_MEM_SHOW(TAG);    // test
            switch (app_event.type) {
                case APP_EVENT_VOICE_WAKEUP_START: {
                    ESP_LOGD(TAG_CTRL_TASK, "WAKEUP_START.");
                    
                    isTurnStart = true;     // 新的一轮语音交互开始

                    // SD已经被挂起,重置定时器以防止:提示音1 与 SD恢复 同时发生
                    if (isTonePending) {   
                        if (xTimerIsTimerActive(timer_voice_cmd)) {
                            if (xTimerReset(timer_voice_cmd, 50 ) != pdPASS ) {
                                ESP_LOGE(TAG_CTRL_TASK, "Failed to restart timer! ");
                            } else ESP_LOGD(TAG_CTRL_TASK, "Timer is restarted.");
                        }
                    } else {    // 挂起条件: (1)正在播放,(2)命令词未触发,(3)此前没被挂起 。 需要防止:多次、连续 唤醒
                        if (!isToneCmd && sd_state == SD_CONTROL_PLAY) {                            
                            isTonePending = true;
                            audio_element_state_t el_state = audio_element_get_state(sd_stream_writer);
                            if (el_state == AEL_STATE_RUNNING) {
                                audio_pipeline_pause(pipeline_sd);
                                ESP_LOGD(TAG_CTRL_TASK, "SD_CONTROL_PLAY is pending by tone-1.");
                            }
                            else {
                                ESP_LOGD(TAG_CTRL_TASK, "In wakeup pending, el_state is: %d.", el_state);
                            }
                        }
                    }
                    break;
                }
                case APP_EVENT_VOICE_WAKEUP_END: {
                    ESP_LOGD(TAG_CTRL_TASK, "WAKEUP_END.");

                    // 开启定时器条件:此前没有命令被识别,同时,一轮完整的语音交互已经开始。如果定时器已经开启,则说明被连续唤醒,需要:重新计时
                    if (!isToneCmd && isTurnStart){
                        if (xTimerIsTimerActive(timer_voice_cmd)) {
                            if (xTimerReset(timer_voice_cmd, 50 ) != pdPASS ) {
                                ESP_LOGE(TAG_CTRL_TASK, "Failed to restart timer! ");
                            } else ESP_LOGD(TAG_CTRL_TASK, "Timer is restarted.");
                        } else {
                            if (xTimerStart(timer_voice_cmd, 0) != pdPASS) {
                                ESP_LOGE(TAG_CTRL_TASK, "Failed to start timer! ");
                            } 
                            else ESP_LOGD(TAG_CTRL_TASK, "Timer is started.");
                        }
                    }
                    break;
                }
                case APP_EVENT_VOICE_CMD_RECOGNIZED: {  // 定时器超时,恢复SD, 命令被识别
                    // CLose Timer
                    if (xTimerIsTimerActive(timer_voice_cmd)) { 
                        xTimerStop(timer_voice_cmd, 0); 
                        ESP_LOGD(TAG_CTRL_TASK, "Timer is stopped by COMMAND_DECT.");
                    }
                    // 播放:提示音2, 防止:连续多次唤醒,导致管道阻塞
                    isToneCmd = true;

                    // 更新:命令词
                    switch (app_event.phrase_id) {
                        case CMD_ID_PLAY:{
                            switch (sd_state) {
                                case SD_CONTROL_PLAY:{
                                    audio_pipeline_resume(pipeline_sd);                                    
                                    ESP_LOGD(TAG_CTRL_TASK, "sd is resumed by playing.");
                                    break;
                                }
                                case SD_CONTROL_PAUSE:{
                                    audio_pipeline_resume(pipeline_sd);
                                    ESP_LOGD(TAG_CTRL_TASK, "sd is resumed by pause.");
                                    break;
                                }
                                case SD_CONTROL_NONE:{  // 装载文件并播放
                                    char *url = NULL;
                                    esp_err_t get_url_ret = sdcard_list_current(sdcard_list_handle, &url);
                                    if (get_url_ret == ESP_OK && url != NULL) {

                                        audio_element_set_uri(sd_stream_reader, url);
                                        ESP_LOGD(TAG_CTRL_TASK, "URL: %s", url);

                                        audio_pipeline_reset_ringbuffer(pipeline_sd);
                                        audio_pipeline_reset_elements(pipeline_sd);
                                        audio_pipeline_run(pipeline_sd);
                                        ESP_LOGD(TAG_CTRL_TASK, "sd is loaded and ran.");
                                    }
                                    break;
                                }
                                default:
                                    break;
                            }
                            sd_state = SD_CONTROL_PLAY;   // 更新状态
                            isTonePending = false; // 命令已经执行,无需(在定时器超时时)恢复被提示音1挂起的播放
                            ESP_LOGD(TAG_CTRL_TASK, "sd_state = SD_CONTROL_PLAY, isTonePending = false.");
                            break;
                        }
                        case CMD_ID_PAUSE:{
                            if (sd_state == SD_CONTROL_PLAY) {
                                audio_pipeline_pause(pipeline_sd);
                                sd_state = SD_CONTROL_PAUSE;
                                isTonePending = false; // 命令已经执行,无需(在定时器超时时)恢复被提示音1挂起的播放
                                ESP_LOGD(TAG_CTRL_TASK, "sd is paused. sd_state = SD_CONTROL_PAUSE, isTonePending = false.");
                            }
                            break;
                        }
                        case CMD_ID_PREV:{
                            char *url = NULL;                            
                            audio_pipeline_stop(pipeline_sd);
                            audio_pipeline_wait_for_stop(pipeline_sd);
                            audio_pipeline_terminate(pipeline_sd);

                            sdcard_list_prev(sdcard_list_handle, 1, &url);
                            ESP_LOGD(TAG_CTRL_TASK, "URL: %s", url);
                            //audio_element_set_uri(sd_stream_reader, "/sdcard/nce2_58.mp3");
                            //ESP_LOGD(TAG_CTRL_TASK, "URL: %s", "/sdcard/nce2_58.mp3");
                            audio_element_set_uri(sd_stream_reader, url);

                            audio_pipeline_reset_ringbuffer(pipeline_sd);
                            audio_pipeline_reset_elements(pipeline_sd);
                            audio_pipeline_run(pipeline_sd);

                            sd_state = SD_CONTROL_PLAY;
                            isTonePending = false;
                            ESP_LOGD(TAG_CTRL_TASK, "pipeline_sd is ran by prev-cmd.");
                            break;
                        }
                        case CMD_ID_NEXT:{
                            char *url = NULL;                            
                            audio_pipeline_stop(pipeline_sd);
                            audio_pipeline_wait_for_stop(pipeline_sd);
                            audio_pipeline_terminate(pipeline_sd);

                            sdcard_list_next(sdcard_list_handle, 1, &url);
                            ESP_LOGD(TAG_CTRL_TASK, "URL: %s", url);
                            //audio_element_set_uri(sd_stream_reader, "/sdcard/podcast.mp3");
                            //ESP_LOGD(TAG_CTRL_TASK, "URL: %s", "/sdcard/podcast.mp3");
                            audio_element_set_uri(sd_stream_reader, url);

                            audio_pipeline_reset_ringbuffer(pipeline_sd);
                            audio_pipeline_reset_elements(pipeline_sd);
                            audio_pipeline_run(pipeline_sd);

                            sd_state = SD_CONTROL_PLAY;
                            isTonePending = false;
                            ESP_LOGD(TAG_CTRL_TASK, "pipeline_sd is ran by next-cmd.");
                            break;
                        }
                        case CMD_ID_VOLUP:{
                            audio_board_handle_t bh = audio_board_get_handle(); 
                            if (!bh || !bh->audio_hal) { 
                                ESP_LOGE(TAG_CTRL_TASK, "Failed get board/HAL handle for volume!"); 
                                break; 
                            } 
                            audio_hal_handle_t hal = bh->audio_hal; 
                            int cv = 0; 
                            if (audio_hal_get_volume(hal, &cv) == ESP_OK) { 
                                int nv = cv + VOLUME_STEP; 
                                if (nv > 100) nv = 100; 
                                if (audio_hal_set_volume(hal, nv) == ESP_OK) { 
                                    ESP_LOGD(TAG_CTRL_TASK, "Volume-Up changed: %d -> %d", cv, nv); 
                                } else { 
                                    ESP_LOGE(TAG_CTRL_TASK, "audio_hal_set_volume failed"); 
                                } 
                            } else { ESP_LOGE(TAG_CTRL_TASK, "audio_hal_get_volume failed"); } 

                            if (isTonePending) {        // 如果 SD 被挂起,恢复,不依赖定时器
                                isTonePending = false;                                
                                audio_pipeline_resume(pipeline_sd);
                                sd_state = SD_CONTROL_PLAY;
                                ESP_LOGD(TAG_CTRL_TASK, "SD-PLAYING is resumed by SD_CONTROL_VOLUP.");
                            }
                            break;
                        }
                        case CMD_ID_VOLDN:{
                            audio_board_handle_t bh = audio_board_get_handle(); 
                            if (!bh || !bh->audio_hal) { 
                                ESP_LOGE(TAG_CTRL_TASK, "Failed get board/HAL handle for volume!"); 
                                break; 
                            } 
                            audio_hal_handle_t hal = bh->audio_hal; 
                            int cv = 0; 
                            if (audio_hal_get_volume(hal, &cv) == ESP_OK) { 
                                int nv = cv - VOLUME_STEP; 
                                if (nv < 0) nv = 0; 
                                if (audio_hal_set_volume(hal, nv) == ESP_OK) { 
                                    ESP_LOGD(TAG_CTRL_TASK, "Volume-Dn changed: %d -> %d", cv, nv); 
                                } else { 
                                    ESP_LOGE(TAG_CTRL_TASK, "audio_hal_set_volume failed"); 
                                } 
                            } else { ESP_LOGE(TAG_CTRL_TASK, "audio_hal_get_volume failed"); } 

                            if (isTonePending) {
                                isTonePending = false;
                                audio_pipeline_resume(pipeline_sd);
                                sd_state = SD_CONTROL_PLAY;
                                ESP_LOGD(TAG_CTRL_TASK, "SD-PLAYING is resumed by SD_CONTROL_VOLDN.");
                            }
                            break;
                        }
                        default:
                            ESP_LOGW(TAG_CTRL_TASK, "Unknown command phrase_id: %d. No ack tone.", app_event.phrase_id);
                            isToneCmd = false;
                            ESP_LOGE(TAG_CTRL_TASK, "cur_cmd = SD_CONTROL_NONE, isToneCmd = false.");
                            break;
                    }
                    isToneCmd = false;          // 确保所有命令执行完成后复位,避免定时器恢复
                    isTurnStart = false;        // 新的一轮语音交互结束
                    ESP_LOGD(TAG_CTRL_TASK, "Command is done. cur_cmd = SD_CONTROL_NONE.");
                    break;
                }                
                case APP_EVENT_SD_PLAYBACK_FINISHED: {
                    audio_pipeline_stop(pipeline_sd);
                    audio_pipeline_wait_for_stop(pipeline_sd);
                    audio_pipeline_terminate(pipeline_sd);
                    audio_pipeline_reset_ringbuffer(pipeline_sd);
                    audio_pipeline_reset_elements(pipeline_sd);

                    isToneCmd = false;          // 确保所有命令执行完成后复位,避免定时器恢复
                    isTurnStart = false;        // 新的一轮语音交互结束
                    sd_state = SD_CONTROL_NONE; // 状态 重置
                    ESP_LOGD(TAG_CTRL_TASK, "sd-player is finished.");
                    break;
                }
                case APP_EVENT_LATE_CMD_TIMEOUT: {
                    ESP_LOGD(TAG_CTRL_TASK, "LATE_CMD_TIMEOUT. isToneCmd: %d, isTonePending: %d", isToneCmd, isTonePending);
                    
                    if (isToneCmd) {    // 从命令词识别事件被接收,到处理完成的时段内,不允许恢复SD.
                        ESP_LOGD(TAG_CTRL_TASK, "A command is active (isToneCmd=true), timer will not resume SD.");
                    } 
                    else if (isTonePending) {
                        isTonePending = false;

                        audio_element_state_t el_state = audio_element_get_state(sd_stream_writer);
                        ESP_LOGD(TAG_CTRL_TASK, "Timer expired. el_state is: %d.", el_state);
                        if (el_state == AEL_STATE_PAUSED) {
                            audio_pipeline_resume(pipeline_sd);
                            ESP_LOGD(TAG_CTRL_TASK, "Timer expired. SD-PLAYING is resumed.");
                            sd_state = SD_CONTROL_PLAY;
                        } 
                        else if (el_state == AEL_STATE_INIT) {
                            audio_pipeline_run(pipeline_sd);
                            ESP_LOGD(TAG_CTRL_TASK, "Timer expired. SD-PLAYING is ran.");
                            sd_state = SD_CONTROL_PLAY;
                        } 
                        else
                            ESP_LOGD(TAG_CTRL_TASK, "Timer expired. el_state is: %d.", el_state);
                    } 
                    else {
                        ESP_LOGD(TAG_CTRL_TASK, "Timer expired. No SD pending.");
                    }
                    break;
                }
                default:
                    break;
            }        
        } else {
            ESP_LOGE(TAG_CTRL_TASK, "[Control Task] Failed receive event!"); 
            vTaskDelay(pdMS_TO_TICKS(100));
        }
    }
}

William
Espressif staff
Espressif staff
Posts: 156
Joined: Tue Apr 24, 2018 5:54 am

Re: 语音控制SD卡播放无法正常恢复播放

Postby William » Wed Aug 20, 2025 3:39 am

您这有尝试过结合 https://github.com/espressif/esp-adf/tr ... amples/cli 来做不?

Who is online

Users browsing this forum: No registered users and 1 guest