「原味厨房(The Culinary Lab)」——全语音 3D AI 具身交互智能私人主厨与沉浸式烹饪陪伴交互舱

7-4原味厨房数字人-视频演示

一、项目背景与痛点

对于绝大多数家庭烹饪者而言,真正的难点往往不在“看菜谱”,而在“手上沾着面粉、锅里正冒着油时”的那一刻——你既腾不出手翻手机,也没法停下手里的活去逐字核对步骤。传统的解决方案存在明显断层:

  • 静态图文菜谱:信息密度低、无法实时纠偏,火候过了才发现步骤看错。

  • 普通语音助手:本质是“问答式”而非“对话式”,你问一句它答一句,炒菜中途想插话追问“现在该不该转小火”会被打断重来,节奏割裂。

  • 录播类烹饪视频:无法交互,更无法根据你手边食材临场给建议。

为了还原“炉边有一位米其林主厨随时陪你做菜”的体验,本文将分享一个多模态互动数字人系统的开发实践。系统以魔珐星云(XmovAvatar)SDK 驱动一位 3D 具身交互智能数字人私人主厨 Leo(雷奥),支持全双工实时语音对话——你可以一边翻炒一边开口追问,主厨随时插话回应;配合流式字幕、背景菜品宫格氛围与火候/计时联动,构成一套沉浸式烹饪陪伴交互舱。

作为一名代码基础较弱的开发者,本次开发全程通过 AI Coding 工具辅助,结合魔珐星云 SDK 的 API,几乎由 AI 辅助完成了全部核心链路。本文将从技术架构、全双工状态机与回声规避、核心交互逻辑三个维度,分享整个开发流程与踩坑经验。


二、核心技术亮点与架构

本系统在前端和交互控流层主要实现了以下技术要点:

  1. 多状态机管理:系统维护了包括入驻中(boot)、待机(ready)、倾听(listening)、思考(thinking)、说话(speaking)等在内的核心交互状态,并额外引入“回声守卫”时序窗口。通过状态机的平滑切换,保障视觉资产与音频流的丝滑同步。

  2. 低延迟全双工打断 + 回声规避:结合前端 VAD(语音活动检测)与 SDK 的打断接口(如 interactiveIdle),实现毫秒级打断响应。真正的难点在于数字人自己的外放声会被本端麦克风采集成“回声”导致自我打断——本文给出以 onVoiceStateChange("end") 为真正说完信号、播报期关麦、余音窗口后再恢复采集的完整工程方案。

  3. 大模型流式控流:大模型(火山方舟 Response API)通过 SSE 逐 token 返回,前端实现双端流式字幕——用户回答用打字机逐字呈现,主厨回复随增量 token 实时填充气泡,降低首字响应延迟(RTTC),并借 previous_response_id 实现原生多轮连续对话。

  4. 背景动态氛围引擎:采用 GSAP 移植的 GridMotion 菜品宫格,数字人入驻后四行图片以左右交替方向错峰滑入填充背景,并随鼠标手势视差滑动;同时联动火候指示条与厨房计时器,让“陪伴感”可视化。


三、核心功能与交互效果展示

1. 核心交互体验:随时打断

主厨正在播报做法时,用户可随时开口说话。系统通过 ASR/VAD 实时监测用户语音,一旦检测到插话,立即执行以下链路:

停止当前播报 -> 自动切换至倾听状态 -> 捕获新提问 -> 思考并流式回答 -> 播报结束后再度恢复监听,进入循环。

在这里插入图片描述

2. 双端流式字幕

  • 用户回答:识别完成后以打字机效果逐字流出,呈现“正在聆听并复述”的自然感。

  • 主厨回复:大模型 SSE 每推送一个增量 token,气泡文本即时追加,字幕与唇形/语音同步生长,而非整段突兀出现。

在这里插入图片描述

3. 背景菜品宫格与氛围联动

  • 滑入揭示:未启动数字人时背景完全留空;主厨入驻并完全加载后,#dish-grid 整体淡入,四行菜品图各自从屏幕外(第 1、3 行从右、第 2、4 行从左)以 power3.out 缓动错峰滑入填满背景,末行到位后无缝接管鼠标视差滑动,两端始终超出屏幕“看不到边”。

  • 视觉氛围反馈:待机/倾听态下底部呈现动态音频波形与火候指示条;提到“计时 5 分钟”时右下角计时器自动启动并倒数。

在这里插入图片描述

四、全双工打断与回声规避的底层控制流实现

全双工交互的核心难点在于状态流转的时序控制,而本项目最关键的踩坑是“数字人自己打断自己”——根因是 LiteSDK.speak() 的 Promise 往往在音频真正播放完之前就已 resolve,若此刻立刻重启麦克风,后半句外放声会被采集成用户回答,进而触发新一轮 speak 把当前播报打断。

解决方案:以 SDK 的 onVoiceStateChange("end")(音频真正播完)作为恢复采集的唯一信号,而非以 Promise 为准。完整控流逻辑如下:

// 核心逻辑伪代码:全双工打断 + 回声规避
function handleUserInterrupt() {
    // 1. VAD 检测到用户语音活动,且当前数字人正在播报/思考
    if (avatarState === 'speak' || avatarState === 'think') {
        // 2. 调用 SDK 打断接口,中止当前语音 token 输出
        if (LiteSDK && typeof LiteSDK.interactiveIdle === 'function') {
            LiteSDK.interactiveIdle();
        }
        // 3. 终止在途推理、作废当前播报令牌,强制切至 'listen'
        thinkingAbort.abort();
        turnToken++; speakToken++;
        updateAvatarState('listen');
    }
}

async function speakToAvatar(text) {
    isSpeaking = true;
    stopRecognition();          // 播报期间关麦,避免采集到自己的声音
    await LiteSDK.speak(text, true, true);
    // 注意:speak 的 Promise 常提前 resolve,此处【不立即恢复麦克风】
}

// 以音频真正播放结束为准,而非 speak Promise
onVoiceStateChange = (s) => {
    if (s === 'end') {
        voicePlaying = false;
        onAvatarVoiceEnd();     // 进入回声守卫窗口
    }
};

function onAvatarVoiceEnd() {
    isSpeaking = false;
    echoGuardUntil = Date.now() + 1500;   // 余音窗口:1.5s 内忽略任何识别结果
    setTimeout(() => {
        lastSpokenText = "";              // 守卫结束再清空台词,供 isEcho 比对
        if (!isSpeaking && !voicePlaying) startRecognition();  // 此时才恢复聆听
    }, 1500);
}

完整交互生命周期流程:

  1. 用户开口 -> VAD 触发打断函数(播报中则 interactiveIdle + 关麦)。

  2. 状态切换 -> 状态机切至 listen,捕获用户完整表达。

  3. 识别完成 -> 状态机切至 think,将文本投递给大模型(带 previous_response_id 多轮锚点)。

  4. 流式返回 -> 主厨气泡随 SSE token 实时生长,状态机切至 speak,驱动唇形同步与播报(期间麦克风关闭)。

  5. 音频真正结束 -> onVoiceStateChange("end") -> 回声守卫 1.5s -> 恢复监听,进入下一轮循环。


五、开发踩坑记录与解决方案

1. 编程小白如何利用 AI 工具攻克复杂 SDK?

在不熟悉复杂多媒体前端开发的情况下,利用 Cursor / Copilot 等 AI 助手是快速落地的关键。

  • 技巧:不要把整个 SDK 文档直接丢给 AI。应该将魔珐星云 SDK 提供的官方 Demo 代码核心 API 说明(如状态机字典、speak 方法入参、onVoiceStateChange 事件) 提取成 Context 喂给 AI,让其定向生成状态切换与打断逻辑代码。本项目中的入驻、播报、打断、音频状态回调均由此方式完成。

2. 数字人“自己打断自己” + 回声问题

  • 现象:主厨刚开口播报,后半句还没说完就被“自己”打断,循环重启麦克风又把外放声当成用户回答。

  • 根因speak() 的 Promise 在音频真正结束前就 resolve;若以此为准立即 startRecognition(),余音窗口内本端 ASR 会把数字人自己的声音转写成“用户回答”,进而触发新一轮 speak

  • 解决

    1. 播报前 stopRecognition() 关麦,onend 自愈重启加 !isSpeaking && !voicePlaying 守卫;

    2. onVoiceStateChange("end") 作为真正说完信号,而非 Promise;

    3. 播完设 1.5s echoGuardUntil 余音窗口,窗口内 handleFinalTranscript 直接丢弃结果,窗口结束再恢复采集;

    4. 守卫期内保留 lastSpokenText,用 isEcho() 文本比对做二次兜底。

3. 背景宫格被 Flex 布局压缩

  • 现象:菜品图宽度被压成“高高的扁扁的”,滑动时两侧露出大片空白。

  • 根因.gm-containerdisplay:flex 父容器的子项,未设 flex-shrink:0,flex 默认把它从设定的 440vw 压到父级(#stage)宽度,导致整行与每张图都被横向压缩。

  • 解决:给 .gm-containerflex-shrink: 0,使其保留真实 440vw 宽度,图片恢复原始比例,滑动时两端始终超出屏幕。


六、总结与后续演进方向

通过本次技术原型的搭建,可以看出“低门槛工具辅助 + 模块化数字人 SDK(魔珐星云)”已经可以让非资深研发快速搭建出具备多模态交互能力的应用。系统的状态机与回声规避设计灵活,极易扩展到其他垂直领域(导购、讲解、陪练等)。

下一步的优化迭代方向:

  • RAG 菜谱知识库挂载:接入私有垂直菜谱/食材库,提升主厨回答的专业度与准确率,支持“我家有 XX 食材能做什么”。

  • AEC 回声消除升级:当前以“播报期关麦”规避回声,代价是播报中无法抢话打断;后续可引入 WebRTC MediaStreamTrack + echoCancellation,在保留插话能力的同时彻底消除回声。

  • 多语种与情感识别:结合 Web Audio API 分析用户语气振幅,调整主厨回应情感,并拓展中英文切换陪伴能力。

  • 移动端与离线适配:将氛围引擎与流式链路向移动端 H5 收敛,适配弱网与触屏交互。

数字人SDK部分参考官方文档——https://xingyun3d.com/?utm_campaign=daily&utm_source=juzhen

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐