修复补丁参考/TuyaOS AI · T5/音频卡死 → 无声

播放器「卡死 / 无声」修复补丁(diff 参考)

针对工单 t=9433 的三层根因,改动落在三个文件:底层 治本(tkl_ao_put_frame 加超时)+ 播放器层 兜底(队列加深、投递有界超时、修内存泄漏)。下方以 diff 形式列出每处改动,可直接对照引用。

平台
T5 · 3.13.3
改动文件
3
关键码
-26369 / -2
编译
未自动编译

改动总览

文件改动要点类型
vendor/T5/tuyaos/tuyaos_adapter/src/driver/tkl_audio.c tkl_ao_put_frame:无限 goto 重试 → 累计 1s 超时,超时丢帧返回 OPRT_TIMEOUT;新增两个宏 治本
apps/.../audio_player/src/ai_player.h 新增 PLAYER_QUEUE_DEPTH=8PLAYER_CMD_POST_TIMEOUT_MS=200 兜底
apps/.../audio_player/src/svc_ai_player.c 队列深度 2→8;start/stop 投递超时 0→200ms;修 start 投递失败时 mm_strdup 泄漏 兜底
为何要配套 有了底层 1s 超时,播放线程最坏 ~1s 脱困;队列加深到 8 + 投递 200ms 超时正好覆盖这个恢复窗口,使 -26369(队列满投递失败)与随后的 -2(start 被拒)不再发生。单改其一不足以根除「隔夜唤醒无声」。
01

tkl_audio.c · 治本

vendor/T5/tuyaos/tuyaos_adapter/src/driver/tkl_audio.c 底层 · 治本
①新增超时宏。ring buffer 仅 DRIVER_SPEAK_FIFO_FRAME_NUM(2)×20ms=40ms,正常播放「满」最多等 ~40ms,1s 超时不会误伤。
@@ 紧接 FRAME_SIZE_PER_20MS 宏定义之后 @@
 #define FRAME_SIZE_PER_20MS(x)  (x * CHANNEL_NUM * TIME_SAMPLE_MS / MS_PER_SEC)

// speaker ring buffer 写满时的重试间隔与累计超时。
// ring buffer 仅 DRIVER_SPEAK_FIFO_FRAME_NUM*20ms=40ms, 正常播放时"满"最多等约 40ms 即被 DAC 排空,
// 故 1s 超时不会误伤正常播放; 一旦 DAC 不消费(如待机唤醒后未真正恢复)即可在 1s 内丢帧退出,
// 避免播放线程被无限重试永久 wedge 住。
#define SPK_WRITE_RETRY_INTERVAL_MS  (20)
#define SPK_WRITE_TIMEOUT_MS         (1000)
②把 tkl_ao_put_frame 里的无限 goto write_spk_retry 换成带累计超时的 do/while,超时即丢帧返回 OPRT_TIMEOUT,让上层 player 线程得以 stop + 恢复。
@@ OPERATE_RET tkl_ao_put_frame(...) 内分段写循环 @@
    // 分段调用 bk_aud_intf_write_spk_data
    while (remaining_size > 0) {
        chunk_size = (remaining_size > spk_ringbuf_size) ? spk_ringbuf_size : remaining_size;

write_spk_retry:
        ret = bk_voice_write_frame_data(g_voice_write_handle, (char *)(pframe->pbuf + offset), chunk_size);
        // ret = bk_voice_write_spk_data(...);
        if (ret == 0) {
            tkl_system_sleep(20);
            goto write_spk_retry;
        }
        // ring buffer 满时 bk_voice_write_frame_data 返回 0, 这里带累计超时重试,
        // 防止 DAC 不消费(如待机唤醒后未真正恢复)导致播放线程永久 wedge。
        UINT_T waited_ms = 0;
        do {
            ret = bk_voice_write_frame_data(g_voice_write_handle, (char *)(pframe->pbuf + offset), chunk_size);
            if (ret != 0) {
                break;
            }
            tkl_system_sleep(SPK_WRITE_RETRY_INTERVAL_MS);
            waited_ms += SPK_WRITE_RETRY_INTERVAL_MS;
        } while (waited_ms < SPK_WRITE_TIMEOUT_MS);

        if (ret == 0) {
            // 超时仍写不进, 判定为底层未在消费, 丢弃本帧并返回, 让上层 player 线程得以 stop+恢复
            os_printf("audio spk write timeout(%u ms), drop frame, chunk:%d \r\n", waited_ms, chunk_size);
            return OPRT_TIMEOUT;
        }

        if (ret < 0) {
            os_printf("audio intf spk semaphore wait fail, ret:%d \r\n", ret);
            return ret;
        }
        offset += chunk_size;
        remaining_size -= chunk_size;
    }
    return OPRT_OK;
部署提醒 vendor/T5/… 是 CDE/embcli 按 make.yaml 下载的依赖,重跑 prepare.sh / embcli update 可能覆盖此文件。要长期生效需反馈给 T5 adapter 上游,或在 prepare 后确认改动仍在。
02

ai_player.h · 兜底宏

apps/tuyaos_demo_wukong_ai/src/miscs/audio_player/src/ai_player.h 播放器层 · 兜底
@@ 紧接 PLAYER_MAX_VOLUME 宏定义之后 @@
 #define PLAYER_IDLE_TIMEOUT_MS  0xFFFFffff
 #define PLAYER_BUSY_TIMEOUT_MS  1
 #define PLAYER_MAX_VOLUME     100

// 兜底: 命令队列深度从 2 加深, 避免播放线程短暂卡顿时 START/STOP 投递立刻失败(-26369);
// 投递给一个有界超时而非 0, 在底层恢复(见 tkl_ao_put_frame 超时)的窗口内重试投递。
#define PLAYER_QUEUE_DEPTH        8
#define PLAYER_CMD_POST_TIMEOUT_MS 200
03

svc_ai_player.c · 兜底应用

apps/tuyaos_demo_wukong_ai/src/miscs/audio_player/src/svc_ai_player.c 播放器层 · 兜底
①队列深度 2 → PLAYER_QUEUE_DEPTH(位于 tuya_ai_player_service_init)。
    TUYA_CALL_ERR_RETURN(tal_queue_create_init(&s_ai_player_ctx.queue, SIZEOF(AI_PLAYER_MSG_T), 2));
    TUYA_CALL_ERR_RETURN(tal_queue_create_init(&s_ai_player_ctx.queue, SIZEOF(AI_PLAYER_MSG_T), PLAYER_QUEUE_DEPTH));
tuya_ai_player_start:投递超时 0 → 200ms,且失败时释放 mm_strdup 出来的 value(修内存泄漏)。
    msg.param.cmd_start.value = value ? mm_strdup(value) : NULL;
    PR_DEBUG("start player %d %d", src, codec);
    TUYA_CALL_ERR_RETURN(tal_queue_post(s_ai_player_ctx.queue, &msg, 0));
    rt = tal_queue_post(s_ai_player_ctx.queue, &msg, PLAYER_CMD_POST_TIMEOUT_MS);
    if (OPRT_OK != rt) {
        PR_ERR("post start cmd failed: %d", rt);
        if (msg.param.cmd_start.value) {
            Free(msg.param.cmd_start.value);
        }
        return rt;
    }
tuya_ai_player_stop:投递超时 0 → 200ms
    msg.cmd  = PLAYER_CMD_STOP;
    PR_DEBUG("stop player %d", player->mode);
    TUYA_CALL_ERR_RETURN(tal_queue_post(s_ai_player_ctx.queue, &msg, 0));
    TUYA_CALL_ERR_RETURN(tal_queue_post(s_ai_player_ctx.queue, &msg, PLAYER_CMD_POST_TIMEOUT_MS));
验证建议 编译后重点验证隔夜待机 → 唤醒:应有声、唤醒处无 1~2s 卡顿、日志不再出现 -26369 / -2;若 DAC 真未恢复,应看到 audio spk write timeout 丢帧日志而非整机 hang。