体验台
| 资源 | 地址 | 说明 |
|---|---|---|
| 官方文档 | https://platform\\.vidu\\.cn/docs/vidu\\-s1 | ViduS1官方文档,更详细的接入教程 |
| Agent Skills | https://github\\.com/shengshu\\-ai/vidu\\-s1\\-api/tree/main/skills/vidu\\-s1\\-api | 开发者可快速体验 |
| 邀请码【8O2QQNQE1O】 | platform.vidu.cn/account-ove... | 赠送积分立刻体验20分钟与S1数字人的互动 |
3 秒决策区
-
这是什么:Vidu S1 实时交互式数字人,单张图直出形象,实时对话,官方称支持 1 分钟到 2 小时的连续互动无质量损失。
-
和别的数字人有什么不同 :它不是"播放预录视频",而是双向流式------你说话它能听见并打断自己的输出,它说话你能随时插嘴。
-
接起来难不难 :3 步。创建会话 → 建 WebSocket 发
conn_init→ 接入 AliRTC 拉流。核心代码不超过 100 行。 -
坑在哪 :
NOT_READY、token 1 小时过期、video 模式"创建成功 ≠ 能说话"、13 种hangup_reason要分开处理。下面都有解。 -
读完你能拿走:一条可跑通的最小链路、一份参数调优对照表、一份错误码速查表。
一、先看结果:这段视频没有剧本

一场密钥失窃案,侦探挨个盘问嫌疑人。
角色说的每一句话、每一个表情,都是模型实时生成 的------没有台词本,没有预录素材,没有"下一句该接什么"的状态机。我第一遍看完又倒回去看了一遍,不是因为画质有多惊艳,而是因为我不知道它下一句要说什么。
这一点很关键。
过去我们说的"数字人",绝大多数是"播放器":提前写好话术库,用 TTS 驱动口型,靠关键词命中来分支。用户问到话术库外的问题,就只能"我没听懂,换个话题问问吧"。
Vidu S1 想做的是另一件事:让数字人从播放 变成在场。
二、为什么开发者该关心:架构变了,接入方式就必须变
这是很多人第一次接实时数字人会卡住的地方------它不是一个 HTTP 接口。
传统 AI 服务的调用模型是这样的:
Plaintext
请求 → 等待 → 返回完整结果
而实时数字人是全双工长连接。你和它同时在说话、同时在听,媒体流和控制信令走两条完全独立的通道:
| 通道 | 承载内容 | 技术选型 |
|---|---|---|
| 控制信令 | 建连、开始、发文字、打断、挂断 | WebSocket(文本 JSON) |
| 媒体流 | 你的麦克风/摄像头、数字人的音视频 | AliRTC |
这两条通道必须同时存在,缺一不可。 很多"数字人接不进去"的问题,本质上都是只打通了一条。
这个架构多出来的复杂度,换来的东西也很实在:
-
真双向感知------它能"看见"你(video 模式下摄像头画面)、"听见"你,并在你开口时中断自己
-
可打断------这是体验分水岭。不能打断的对话,本质上还是广播
-
长时程------连续互动不降质,这是"陪伴"类场景的硬门槛
三、整条链路,一张图说完
Plaintext
sequenceDiagram
participant App as 你的 App
participant API as Vidu HTTP API
participant WS as Vidu WebSocket
participant RTC as AliRTC
App->>API: ① POST /live/v1/lives
API-->>App: live.id + rtc.token + rtc.user_id
App->>WS: ② 连接 /live/ws/live/connect?live_id=xxx
App->>WS: 发送 conn_init
WS-->>App: conn_init_ack.success = true
Note over App,WS: 控制链路就绪
App->>RTC: ③ joinChannel(rtc.token, rtc.user_id)
App->>RTC: publishLocalAudioStream(true)
App->>RTC: subscribe(数字人流)
RTC-->>App: 数字人音视频 / 音频
Note over App,RTC: 开始实时对话
App->>WS: text_msg / audio_interrupted / call_hangup
记住这三步的顺序,以及一个反直觉的点:第 ① 步成功 ≠ 可以开始说话。 原因见第五节坑位 2。
四、三步跑通:可直接复制的最小链路
所有 HTTP 接口都需要 Header:
Authorization: Token vda_xxx
4.0 接口总览(先看这里找入口)
| 方法 | 路径 | 用途 |
|---|---|---|
POST |
/live/v1/lives |
创建会话(核心入口) |
GET |
/live/v1/lives/{live_id} |
查询会话状态与账单 |
GET |
/live/v1/lives |
列表查询 |
WebSocket |
/live/ws/live/connect |
App 控制信令 |
| --- | AliRTC joinChannel |
实时媒体流 |
POST |
/live/v1/voices/clone |
音色克隆 |
GET |
/live/v1/voices |
查询自定义音色 |
POST/PUT |
/tools/v2/files/uploads |
图片上传(三步式) |
4.1 创建会话
HTTP
POST https://{host}/live/v1/lives
Authorization: Token vda_xxx
Content-Type: application/json
最小请求体(只需要 3 个字段就能跑起来):
JSON
{
"call_mode": "video",
"avatar": {
"persona": "你是一个友好的客服,请自然地与用户实时互动",
"image_uri": "https://你的数字人图片地址.png",
"voice": ""
}
}
完整参数版(生产环境建议至少配这些):
JSON
{
"call_mode": "video",
"character_id": "1",
"avatar": {
"persona": "你是甜甜 Tina,我的声音像温热的奶茶,甜甜的、暖暖的,但解决问题可一点都不含糊哦!",
"image_uri": "https://scene.cf.vidu.studio/media-asset/070302-ZFkgvJxBTM0ZQJoO.png",
"voice": "Tina",
"greeting_instruction": "直播刚刚开始。请用中文主动向观众说一句简短、温暖、自然、符合人设的开场白。直接说出来,不要用旁白叙述。",
"farewell_enabled": true,
"idle_action": true,
"persona_enhance": true
},
"audio": { "enable_transcription": true },
"vad": {
"type": "semantic",
"threshold": 0.5,
"silence_duration_ms": 200,
"idle_timeout_ms": 500
},
"llm": {
"temperature": 0.7,
"top_p": 0.8,
"top_k": 20,
"frequency_penalty": 1,
"presence_penalty": 0.3,
"seed": -1,
"max_tokens": 50
},
"idle_timeout_seconds": 7200,
"memory_retrieval": {
"enabled": true,
"endpoint": "https://api.example.com/memory/search",
"authorization": "Bearer memory-api-token",
"timeout_ms": 3000
},
"knowledge_retrieval": {
"enabled": true,
"endpoint": "https://api.example.com/knowledge/search",
"authorization": "Bearer knowledge-api-token",
"timeout_ms": 3000
}
}
响应:
JSON
{
"live": {
"id": "123456789",
"status": "waiting",
"live_duration": 600,
"call_mode": "video"
},
"rtc": {
"app_id": "xxxx",
"channel_id": "live-user-123456789",
"user_id": "live-user-1001-123456789",
"token": "base64-token...",
"token_expire_at": "1750003600"
}
}
务必保存这三个值,后面每一步都要用:
| 字段 | 用途 |
|---|---|
live.id |
房间 ID,WebSocket 建连、查询账单、发文本全靠它 |
rtc.token |
加入 RTC 频道的凭证,默认 1 小时有效 |
rtc.user_id |
你在 RTC 频道里的用户名 |
⚠️ 注意
live.status此时是waiting------数字人还没准备好。
4.2 建立 WebSocket 并发起信号
Bash
wss://{host}/live/ws/live/connect?live_id={live_id}
Authorization: Token vda_xxx
连接建立后立刻 发送 conn_init:
JSON
{
"type": 1,
"live_id": "123456789",
"seq_id": 1,
"payload": {
"conn_init": { "version": 1 }
}
}
收到 conn_init_ack.success = true,才说明控制链路初始化完成。
JavaScript
// 建连 + 指数退避重试(video 模式强烈建议加)
async function initControlChannel(liveId, token) {
const ws = new WebSocket(
`wss://${HOST}/live/ws/live/connect?live_id=${liveId}`
);
ws.onopen = () => {
ws.send(JSON.stringify({
type: 1,
live_id: liveId,
seq_id: 1,
payload: { conn_init: { version: 1 } }
}));
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.payload?.conn_init_ack?.success) {
console.log('[WS] 控制链路就绪');
} else if (msg.payload?.conn_init_ack?.reason === 'NOT_READY') {
// 数字人渲染侧还没回连,等 2~3 秒重试
setTimeout(() => ws.send(buildConnInit()), 2500);
}
};
// 兜底:必须监听 close / error
ws.onclose = (e) => console.warn('[WS] closed', e.code, e.reason);
ws.onerror = (e) => console.error('[WS] error', e);
}
4.3 接入 AliRTC,把画面和声音跑起来
JavaScript
await aliRtc.joinChannel(rtc.token, rtc.user_id);
await aliRtc.publishLocalAudioStream(true);
if (callMode === 'video') {
await aliRtc.publishLocalVideoStream(true);
}
两种模式的推/订阅关系不一样,别搞混:
| 模式 | 加入频道 | 推流 | 订阅 |
|---|---|---|---|
audio |
live-audio-{liveID} |
麦克风音频 | 数字人音频 live-bot-... |
video |
live-user-{liveID} |
麦克风 + 摄像头 | 数字人音视频 live-video-push-... |
实际渲染数字人,还需要订阅远端音视频,并在订阅状态变为 subscribed 后绑定视频容器。
五、参数怎么调:这才是拉开体验差距的地方
文档会告诉你每个字段的取值范围,但不会告诉你该怎么选。这一段是我自己调下来的经验。
5.1 vad:决定"打断手感",优先级最高
| 字段 | 默认 | 怎么调 |
|---|---|---|
vad.type |
server |
server 会过滤附和词和背景音;semantic 是用户开口即打断 。要做"自然对话"选 semantic,要防止误打断选 server |
vad.threshold |
0.5 | 拟声词/嘈杂声屏蔽力度,值越低屏蔽越少。嘈杂环境往上调 |
vad.silence_duration_ms |
400 | 语音结束后静音多久触发响应。调小→响应快但容易抢话;调大→稳但显迟钝 |
vad.idle_timeout_ms |
0 | 静默多久后模型主动开口。做陪伴类场景建议设 3000~5000,让它会主动找话题 |
💡 我的建议 :先用
semantic+silence_duration_ms: 200跑起来,这个组合最接近真人对话节奏。觉得太敏感再切server慢慢调 threshold。
5.2 llm:控制"嘴瓢"和"话痨"
| 字段 | 默认 | 直觉理解 |
|---|---|---|
temperature |
0.7 | 创意度。客服/导购压到 0.4~0.6;陪伴/角色扮演拉到 0.9+ |
top_p |
0.8 | 质量下限,和 temperature 二选一调即可 |
top_k |
20 | 候选词范围 |
frequency_penalty |
1.0 | 防嘴瓢。<1 反向鼓励重复;>1 惩罚重复。数字人反复说同一句口头禅就往上加 |
presence_penalty |
0.3 | 话题广度,值大更容易换话题 |
max_tokens |
50 | 实时场景别调大。50 token 大概对应一句话,回复太长会严重拖慢节奏 |
seed |
-1 | 固定值可复现,-1 为随机 |
⚠️ 实时对话里,
max_tokens是最容易被忽略的性能杀手。它直接决定"你说完到它开口"的等待时长。
5.3 avatar:像不像人,看这几个字段
| 字段 | 说明 |
|---|---|
persona |
人设提示词,5 万字以内,空间远超你的想象,值得认真写 |
persona_enhance |
true 会让平台帮你扩写优化提示词。自己写不好就打开 |
greeting_instruction |
开场白提示词,≤200 字符。默认就在打招呼,想要别的开场一定要覆盖 |
idle_action |
不说话时是否做自然动作。强烈建议开,静止不动的数字人很出戏 |
farewell_enabled |
结束时自动生成几秒"礼貌离开"视频,体验完整度提升明显 |
图片要求:仅支持 1 张单人图 ,PNG/JPG/JPEG/WEBP,≤50MB;base64 需带 data:image/png;base64, 前缀。
5.4 idle_timeout_seconds:别让僵尸会话烧钱
默认 7200 秒(2 小时),范围 10~7200。用户关掉页面但没挂断时,会话会一直挂着计费。实际业务里建议按场景收紧,比如客服场景设 600。
5.5 外调记忆与知识库(内测)
memory_retrieval 和 knowledge_retrieval 可以把你自己的记忆/知识系统挂进来,让数字人有长期记忆、能回答业务问题。
JSON
{
"enabled": true,
"endpoint": "https://api.example.com/memory/search",
"authorization": "Bearer memory-api-token",
"timeout_ms": 3000
}
四个字段都要注意:endpoint 必须是平台可访问的 http(s) 绝对 URL;authorization 会作为 Authorization header 转发且校验非空 ;timeout_ms 最大 30000。
这个能力是内测版。它决定了你的数字人能不能"记住上周聊过什么"------做长期陪伴产品的话,这是核心。
六、避坑清单:这 6 个坑我建议先读
坑 1:NOT_READY 不是报错,别慌
video 模式下,数字人渲染侧需要时间回连。这时候发 conn_init 会收到 NOT_READY。
正确做法 :等待 2~3 秒重试,用指数退避。不要立刻重建页面或重新创建会话------那只会让等待更久。
坑 2:video 模式"创建成功 ≠ 能说话"
这是最反直觉的一点。POST /live/v1/lives 返回 200,只代表房间和外部 stream 已创建,不代表渲染侧就绪。
判断是否真的 ready,唯一标准是收到 conn_init_ack.success = true。
坑 3:token 只有 1 小时
rtc.token 默认 1 小时有效,过期时间看 rtc.token_expire_at。长会话必须自己实现续期逻辑,否则用户聊到第 61 分钟直接断线。
坑 4:403 是因为跨 Key / 跨账号
403 的典型原因是拿 A 的 API Key 去访问 B 创建的 live_id。不要跨账号复用 ****live_id。
坑 5:App WebSocket 只能发文本 JSON
不要试图通过 App WS 发送二进制音频或视频 ,媒体流只能走 AliRTC。协议里预留的 audio_start、audio_stop、emotion_signal 当前接入不要求客户端发送。
另外,除了 conn_init 之外,text_msg、audio_interrupted、call_hangup 当前没有独立的业务 ack,别在代码里傻等。
坑 6:13 种 hangup_reason 必须分开处理
被强制断开时你会收到:
JSON
{
"type": 6,
"payload": {
"hangup": { "hangup_reason": "xxxx" }
}
}
按类别给出不同的用户提示,体验差别很大:
| 分类 | hangup_reason |
该怎么做 |
|---|---|---|
| 正常结束 | user_end, timeout |
正常收尾,不需要报错 |
| 账户问题 | credit_insufficient |
必须弹出"积分不足"提示,否则用户以为产品坏了 |
| 风控 | audit_violation |
提示内容违规,但措辞要柔和 |
| 网络/链路 | sip_closed, provider_closed, sip_reconnect_timeout, client_reconnect_timeout |
提示网络异常 + 提供重连按钮 |
| 服务端调度 | prepared_sip_disconnected, owner_taken_over, owner_lease_lost |
一般可直接静默重连 |
| AI 侧 | ai_output_closed |
输出通道异常,建议重建会话 |
| 其他 | external |
兜底提示 |
七、错误码速查
HTTP 错误
统一错误结构:
JSON
{
"code": 400,
"reason": "BAD_REQUEST",
"message": "参数非法",
"metadata": {}
}
| 状态码 | 典型原因 | 处理建议 |
|---|---|---|
400 |
参数非法/字段缺失/call_mode 非法/文本超长/会话状态不允许 |
按字段说明修正;live 已结束就重建 |
401 |
API Key 缺失、格式错误或无效 | 检查 Authorization: Token vda_xxx,确认 Key 属于当前环境 |
403 |
访问了不属于当前 Key/账号的 live | 用创建时的同一个 Key |
404 |
会话不存在/已过期/live_id 写错 |
确认 live_id;无法恢复就重建 |
500 |
服务内部依赖异常(DB、SIP/video provider、AliRTC token 等) | 可短暂重试;持续失败带上 live_id + trace_id + 请求时间反馈 |
WebSocket 错误
| 错误码 | 原因 | 处理建议 |
|---|---|---|
NOT_READY |
video 模式渲染侧未回连完成 | 等 2~3 秒重试 conn_init,指数退避 |
LIVE_CONN_INIT_FAILED |
初始化失败,会话状态或服务端依赖异常 | 关闭当前 WS,重新创建 live;仍失败则带日志反馈 |
兼容性提醒
-
除监听
conn_init_ack外,必须 监听close/error做兜底 -
stream_url当前是保留字段,实时播放请用rtc信息接入 AliRTC
八、写在最后
回看开头那段没有剧本的侦探视频。
Vidu S1 做的这件事其实很纯粹:让数字人从"播放"变成"在场"。
从开发者角度看,WebSocket + RTC 的架构确实比普通 HTTP 接口多了几步,但换来的是真正的实时双向交互------你说话它能听见,它说话你能打断。在这之前的视频生成产品里,这件事基本做不到。
单图直出、无限时长、准实时响应,加上三步就能跑通的最小链路,接入门槛已经压得相当低了。剩下的想象空间,在各位开发者手里。
未来,Vidu API开放平台将持续分享:
AI 视频创作教程、最佳实践案例、行业解决方案及最新能力解析......
欢迎关注我们,与更多开发者和创作者共同探索 AI 视频的无限可能!