【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

Wi-Fi 设备、蜂窝设备、WuKongAI、开发板、TuyaOS 移植等


Post Reply
Gzz_lin
Posts: 25

目前发现以下两个函数都有概率发生永久阻塞现象:
wukong_audio_player_stop(AI_PLAYER_ALL);
wukong_audio_play_data(AI_AUDIO_CODEC_MP3, data, len);

以下是这两个函数的调用:

Code: Select all

//唤醒进入聆听模式
OPERATE_RET hly_ai_wakeup(void)
{
    tal_mutex_lock(_ai_mutex);
    if(!ai_start)
    {
        tal_mutex_unlock(_ai_mutex);
        return 0;
    }

wukong_audio_player_stop(AI_PLAYER_ALL);
wukong_audio_input_reset();
tuya_ai_agent_event(AI_EVENT_CHAT_BREAK, 0);
wake_up_flag = TRUE;
paly_alart = TRUE;
_hly_ai_set_stat(AI_STAT_LISTEN);
tal_mutex_unlock(_ai_mutex);
wukong_audio_player_alert(AI_TOY_ALERT_TYPE_WAKEUP, FALSE);
return 0;
}

Code: Select all

OPERATE_RET hly_ai_play_alert(HLY_AI_ALERT_E alert)
{
    static HLY_AI_ALERT_E curr_alert=HLY_AI_ALERT_MAX;
    static uint8_t* data=NULL;
    static uint32_t len;
    uint8_t str[32]={0};
    tal_mutex_lock(_alert_mutex);
    TAL_PR_DEBUG("=====hly ai play alert:%d", alert);
    if (curr_alert != alert)
    {
        curr_alert = alert;
        if (data != NULL)
        {
            hly_free_psram(data);
        }
        switch (curr_alert)
        {
        case HLY_AI_ALERT_ALARM:
            sprintf(&str, "/mp3/%s.mp3", "alarm");
            break;
        case HLY_AI_ALERT_CLOCK:
            sprintf(&str, "/mp3/%s.mp3", "clock");
            break;
        case HLY_AI_ALERT_ERROR:
            sprintf(&str, "/mp3/%s.mp3", "error");
            break;
        case HLY_AI_ALERT_EXIT:
            sprintf(&str, "/mp3/%s.mp3", "exit");
            break;
        case HLY_AI_ALERT_START:
            sprintf(&str, "/mp3/%s.mp3", "start");
            break;
        case HLY_AI_ALERT_STOP:
            sprintf(&str, "/mp3/%s.mp3", "stop");
            break;
        case HLY_AI_ALERT_WAKEUP:
            sprintf(&str, "/mp3/%s.mp3", "wakeup");
            break;

    default:
        tal_mutex_unlock(_alert_mutex);
        return -1;
        break;
    }
    TAL_PR_DEBUG("=====hly ai load alert:%s", &str);
    if (hly_fs_load_psram(&str, &data, &len) != 0)
    {
        tal_mutex_unlock(_alert_mutex);
        return -3;
    }
}
if (data == NULL)
{
    tal_mutex_unlock(_alert_mutex);
    return -1;
}
int rt = wukong_audio_play_data(AI_AUDIO_CODEC_MP3, data, len);
tal_mutex_unlock(_alert_mutex);
return rt;
}

Tags:
User avatar
一线长天
Posts: 107

Re: 【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

在 3.13.3 的 tuya_ai_player_stop / tuya_ai_player_start 里给 while 加超时退出:

// tuya_ai_player_stop
UINT_T to = 0;
while (player->state != AI_PLAYER_STOPPED) {
tal_system_sleep(10);
if (to++ > 200) { // 最多 2s,不再死等
PR_ERR("stop timeout mode=%d state=%d", player->mode, player->state);
return OPRT_TIMEOUT;
}
}

// tuya_ai_player_start (SRC_MEM 分支同理,建议 1000 → 10s)

这一步把"永久卡死"降级成"可恢复的超时返回"——能立刻止住整机假死,是必须先打的止血补丁。

这几条能从根本上避免触发上面的机制:

  1. 不要握着锁做阻塞调用。hly_ai_wakeup 里把 wukong_audio_player_stop() 移到 tal_mutex_unlock(_ai_mutex) 之后再调,锁只用来保护 ai_start/状态位的读写。hly_ai_play_alert 同理,wukong_audio_play_data() 不要在持 _alert_mutex 时调。
  2. 绝不在播放事件回调链里直接调 wukong_audio_player_stop/play_data。如果业务确实要在 PLAY_END 后切流程,改成往自己的业务线程/消息队列 post 一个事件,由业务线程去调,切断重入。
  3. 检查 consumer.write 的底层是否会无限阻塞。T5 的 tkl audio 输出最好是有界写(满了丢帧或带超时),保证播放线程任何时候都能在有限时间内回到取队列那一步。
Gzz_lin
Posts: 25

Re: 【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

在software/TuyaOS/apps/tuyaos_demo_wukong_ai/src/miscs/audio_player/src/svc_ai_player.c的tuya_ai_player_stop中有一处死循环风险,在TUYA_CALL_ERR_RETURN(tal_queue_post(s_ai_player_ctx.queue, &msg, 0));之后紧跟了一个死循环去检测状态,但凡__ai_player_thread_cb连续两次获得CPU执行权限,连续处理两条msg,这个死循环就会触发。

User avatar
一线长天
Posts: 107

Re: 【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

看你的代码不是多线程调用,不会有提到的情况。先stop再start,一定是stop先退出了才执行start。针对多线程的情况,A线程stop,B线程start,那么是有概率A线程等不到stopped(即进入死循环)。针对这个情况作了超时2s退出stop接口。

分析了相关代码,我的结论是:这个分析不成立 —— 这里不会死循环。 关键在于它把「轮询持久状态」误当成了「边沿触发的信号」。

代码事实

tuya_ai_player_stop(814-846)的等待逻辑:

TUYA_CALL_ERR_RETURN(tal_queue_post(s_ai_player_ctx.queue, &msg, 0));
UINT_T stop_timeout = 0;
while(player->state != AI_PLAYER_STOPPED) {
tal_system_sleep(10);
if(stop_timeout++ > 200) { // 最多 2s
return OPRT_TIMEOUT;
}
}

而 __cmd_player_stop(115-121)无条件把状态置位:

player->state = AI_PLAYER_STOPPED;

为什么不会死循环

  1. 这是对持久标志位的轮询,不是一次性信号,不存在 "miss wakeup"

调用线程 spin 的是 player->state 这个常驻变量。线程连续处理两条消息也好、调用线程那一刻没抢到 CPU 也好,都无所谓 —— 只要 STOP 被处理过,state 就一直是 STOPPED,调用线程下一次 poll 必然读到。这跟信号量/条件变量不同:那种才会因为「错过那一瞬间的 signal」而永久阻塞。所以「连续处理两条消息导致死循环」的推理链是断的。

  1. __cmd_player_stop 一旦执行就把状态置 STOPPED,不依赖任何顺序

无论 STOP 前面排了几条消息、是否连着被 fetch,处理到 STOP 那一刻状态就是 STOPPED。

  1. 即使状态真被翻回去,也有硬超时兜底

stop_timeout++ > 200(2s)是显式加的保护(注释:"FG 卡住时不再死等")。哪怕出现「STOP 后面又紧跟一条 START 把 state 翻回 PLAYING」这种极端情况,最多 2s 也会 return OPRT_TIMEOUT 退出。这一条就从根本上排除了"死循环"的可能。

真正值得担心的(但跟"两条消息"无关)

如果 tuya_ai_player_stop 被播放线程自己调用(比如在 playlist_cb、consumer 回调里,这些可能跑在 __ai_player_thread_cb 线程上),那就是真正的自死锁:线程卡在 stop 的 while 里,无法再去 dequeue 自己刚 post 的 STOP。不过同样被那个 2s 超时降级成「2s 后返回 TIMEOUT,但 STOP 命令滞留在队列里没处理」—— 这才是潜在 bug,而不是"连续 fetch 两条消息"。

Gzz_lin
Posts: 25

Re: 【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

在3.13.3的demo中tuya_ai_player_stop函数没有超时退出机制,只有tuya_ai_player_start函数有,不知道是不是后续的版本才修复了这个bug

Gzz_lin
Posts: 25

Re: 【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

微信图片_20260629140250_1238_148.png

这是3.13.3中的

Gzz_lin
Posts: 25

Re: 【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

甚至我在3.13.8的版本中依旧发现demo中有这个bug,你说的stop有超时退出是哪个版本或者哪个补丁我就不得而知了。

User avatar
一线长天
Posts: 107

Re: 【求助】T5 tuya_os_sdk3.13.3播放提示音或停止时卡死

修复这个问题的demo还没有发布,后续会带上

Post Reply