数字人实时对话模型(avatar-dialog)能够基于输入的音频和预设的2D数字人形象,实时生成播报视频。本文介绍其WebSocket API交互流程及输入输出参数。
功能及模型介绍
数字人服务端通过 WebSocket 与客户端保持长连接。在连接过程中,服务端会根据输入的音频(仅支持单声道PCM)以及从公共形象库中选择的2D数字人形象,实时生成播报视频,并将生成的视频流推送至视频直播RTC。
注意:为了在客户端展示数字人实时生成的视频流画面,您需要自行从RTC拉取视频流并进行下一步处理。
数字人实时对话模型(avatar-dialog)依托视频直播 RTC 实现推流功能,推流的视频分辨率为 1080P,收费类型为视频通话。RTC推流费用请参考实时音视频费用。
服务端模型简介
模型名称 | 计费单价 | 限流(主账号与RAM子账号共用) | 免费额度 | |
任务下发接口QPS限制 | 同时处理中任务数量 |
模型名称 | 计费单价 | 限流(主账号与RAM子账号共用) | 免费额度 | |
任务下发接口QPS限制 | 同时处理中任务数量 | |||
avatar-dialog | 0.01元/秒 | 1 | 1 | 免费额度:600秒 有效期:百炼开通后180天内 |
数字人实时对话模型(avatar-dialog)目前正处于邀测阶段。您可通过百炼模型广场申请使用,审核通过后方可调用模型并获得免费额度。免费额度用完后,将按计费单价收费。
前提条件
在调用前,您需要开通模型服务并获取API Key,再配置API Key到环境变量。
开通阿里云视频直播服务(RTC),然后创建应用,获取应用ID和AppKey。
请将以下变量配置到环境变量中,具体配置方法请参见配置API Key到环境变量。
RTC_APP_ID
(应用 ID)RTC_APP_KEY
(AppKey)
WebSocket客户端与服务端交互流程
按照时间顺序,客户端与服务端的交互流程如下所示。
第一阶段:初始化
第二阶段:根据音频实时生成视频
第三阶段:触发心跳
第四阶段:结束任务
|
|
WebSocket客户端实现流程
在编写WebSocket客户端代码时,为了同时发送和接收消息,通常采用异步编程。您可以按照以下步骤来编写程序:
建立WebSocket连接:首先,初始化并建立与服务器的WebSocket连接。
异步监听服务器消息:启动一个独立的线程(或使用异步任务,具体实现方式取决于编程语言)来监听服务器返回的消息,并根据消息内容执行相应的操作。
发送消息:在与步骤2中不同的线程中(例如主线程或其他独立线程),向服务器发送消息。
关闭连接:在程序结束前,确保关闭WebSocket连接以释放资源,避免连接泄漏。
本文主要介绍通过WebSocket连接访问服务时的鉴权,以及客户端与服务端之间的消息交互流程。
1. 建立WebSocket链接
调用WebSocket库函数(具体实现方式因编程语言或库函数而异),将请求URL和请求头传入以建立WebSocket连接。
WebSocket URL固定为:
wss://dashscope.aliyuncs.com/api-ws/v1/inference
请求头中需添加如下鉴权信息:
{
"Authorization": "Bearer <YOUR_DASHSCOPE_API_KEY>", // 将<YOUR_DASHSCOPE_API_KEY>替换成您自己的API Key
}
2. 发送并监听消息
建立连接后,客户端可以向服务端发送消息,也可以接收来自服务端的消息。消息均为JSON格式。
action消息:客户端向服务端发送的消息(客户端 → 服务端)。
event消息:服务端向客户端返回的消息(服务端 → 客户端)。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"streaming": "duplex",
"action": "run-task"
},
"payload": {
"input": {
"header": {
"name": ""
},
"payload": {}
}
}
}
字段 | 类型 | 必选 | 描述 | 示例值 |
header | Object | 是 | 请求头 | |
header.task_id | String | 是 | 当次任务ID,建议使用随机生成的32位UUID | 97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx |
header.action | String | 是 | 发送给服务端的事件类型,不同的事件对应不同的action消息:
| run-task |
header.streaming | String | 是 | WebSocket交互方式 | 固定值为 duplex |
payload.input | Object | |||
payload.input.header | Object | 是 | header仅包含name字段,该字段用来区分不同的action消息,其取值包括以下几种: | InitializeVideoSession |
payload.input.payload | Object | 否 | 发送给服务端的关键信息(如请求参数、音频数据等),不同类型的action消息在该字段中包含各自特定的数据结构 |
event消息数据格式如下:
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "task-started"
},
"payload": {
"output": {
"header": {
"name": ""
},
"payload": {}
}
}
}
字段 | 类型 | 描述 |
header | Object | 请求头 |
header.task_id | String | 客户端生成的task_id |
header.event | String | 服务端返回的事件类型,不同的事件对应不同的event消息:
|
payload.output | Object | |
payload.output.header | Object | header仅包含name字段,该字段用来区分不同的event消息,其取值包括以下几种: |
payload.output.payload | Object | 服务端返回的关键信息(如数字人状态、错误信息等),不同类型的event消息在该字段中包含各自特定的数据结构 |
下面将按照WebSocket客户端与服务端的交互流程,介绍对应的action消息和event消息。
action消息:InitializeVideoSession
客户端发送InitializeVideoSession以开启数字人渲染任务。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"streaming": "duplex",
"action": "run-task"
},
"payload": {
"task_group": "aigc",
"task": "video-generation",
"function": "stream-generation",
"model": "avatar-dialog",
"input": {
"header": {
"name": "InitializeVideoSession",
},
"payload": {
"avatar_id": "",
"format": "PCM",
"sample_rate": 16000,
"rtc_param": {
"app_id": "xxx",
"channel_id": "xxx",
"user_id": "xxx",
"nonce": "xxx",
"timestamp": 0,
"token": "xxx",
"gslb": ["xxx"]
},
"user_agent": {
"client": "BAILIAN",
"platform": "Dalvik/2.1.0 (Linux; U; Android 12; NOH-AL00 Build/HUAWEINOH-AL00)",
"version": "x.x.xx",
"app_type": "Dev"
}
}
},
"parameter": {}
}
}
字段 | 类型 | 必选 | 描述 | 示例值 |
字段 | 类型 | 必选 | 描述 | 示例值 |
header | Object | 是 | action消息头 | 参考action消息数据格式 |
header.task_id | String | 是 | 当次任务ID,建议使用随机生成的32位UUID | 97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx |
header.streaming | String | 是 | WebSocket交互方式 | 固定值为 duplex |
header.action | String | 是 | 发送给服务端的事件类型 | 固定值为run-task |
payload | Object | 是 | action消息体 | |
payload.task_group | String | 是 | 任务组 | 固定值为aigc |
payload.task | String | 是 | 任务类别 | 固定值为video-generation |
payload.function | String | 是 | 方法类型 | 固定值为stream-generation |
payload.model | String | 是 | 数字人服务 | 固定值为avatar-dialog |
payload.input | Object | 是 | 请求体的输入内容 | |
payload.input.header | Object | 是 | 输入内容的请求头 | |
payload.input.header.name | String | 是 | 消息名称 | InitializeVideoSession |
payload.input.payload | Object | 是 | 发送给服务端的关键信息 | 见下 |
payload.input.payload参数
字段 | 类型 | 必选 | 描述 | 示例值 |
字段 | 类型 | 必选 | 描述 | 示例值 |
avatar_id | String | 是 | 数字人ID,请在公共形象库中选择某一形象 | taoji |
format | String | 是 | 音频格式,目前仅支持单声道PCM | PCM |
sample_rate | Integer | 是 | 音频采样率(单位Hz),可选值为:
| 16000 |
rtc_param | Obect | 是 | RTC入会参数 您需要开通视频直播RTC服务,请在RTC token获取相关参数 | |
rtc_param.app_id | String | 是 | ||
rtc_param.channel_id | String | 是 | ||
rtc_param.user_id | String | 是 | ||
rtc_param.nonce | String | 是 | ||
rtc_param.timestamp | Integer | 是 | ||
rtc_param.token | String | 是 | ||
rtc_param.gslb | Array | 是 | RTC推流地址 | 固定值为["https://gw.rtn.aliyuncs.com"] |
user_agent | Obect | 否 | 用户设备型号 | |
user_agent.client | String | 是 | 用户设备的客户端 | 固定值为BAILIAN |
user_agent.platform | String | 否 | 用户拉流端平台拉,可选值为:
| Android |
event消息:VideoSessionInitialized
客户端会收到服务端发送的VideoSessionInitialized消息,表示数字人服务已收到初始化任务并开始处理。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "result-generated"
},
"payload": {
"output": {
"header": {
"name": "VideoSessionInitialized"
},
"payload": {
}
}
}
}
event消息:VideoSessionStarted
客户端会收到服务端发送的VideoSessionStarted消息,表示数字人画面已成功推流,标志着初始化完成。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "result-generated"
},
"payload": {
"output": {
"header": {
"name": "VideoSessionStarted"
},
"payload": {
}
}
}
}
action消息:GenerateVideo
客户端调用GenerateVideo
发送音频,开启数字人实时播报。该消息对每次发送的音频长度没有严格限制,通过speech_id
和sentence_id
来标识和描述音频内容。
例如,当需要数字人播报一段10秒的音频时,这一段完整的播报被称为一个speech。这10秒的音频可以通过多次调用GenerateVideo
以流式方式发送至服务端,只需确保这些消息具有相同的speech_id
即可。
此外,一个speech可以包含多个sentence(句子)。每当一个sentence开始播报时,客户端会收到服务端发送的SentenceStarted消息。sentence主要用于字幕对齐,实现语音与文字的实时同步。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"action": "continue-task"
},
"payload": {
"input": {
"header": {
"name": "GenerateVideo"
},
"payload": {
"speech_id": "speech_id",
"sentence_id": "sentence_id",
"audio_data": "base64==",
"end_of_speech": false
}
}
}
}
字段 | 类型 | 必选 | 描述 | 示例值 |
字段 | 类型 | 必选 | 描述 | 示例值 |
header | Object | 是 | action消息头 | 参考action消息数据格式 |
header.task_id | String | 是 | 当次任务ID,建议使用随机生成的32位UUID | 97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx |
header.action | String | 是 | 发送给服务端的事件类型 | 固定值为continue-task |
payload | Object | 是 | action消息体 | |
payload.input | Object | 是 | 请求体的输入内容 | |
payload.input.header | Object | 是 | 输入内容的请求头 | |
payload.input.header.name | String | 是 | 消息名称 | GenerateVideo |
payload.input.payload | Object | 是 | 发送给服务端的关键信息 | 见下 |
payload.input.payload参数
字段 | 类型 | 必选 | 描述 | 示例值 |
字段 | 类型 | 必选 | 描述 | 示例值 |
speech_id | String | 是 | speech ID(数字人的一段完整播报被称为一个speech),该值由客户端自定义,需要确保在同一次会话(session)中不变 | |
sentence_id | String | 是 | 用于划分播报内容,该值由客户端自定义,需要确保在同一次会话(session)中不变 | |
audio_data | String | 是 | Base64编码格式的音频数据,您可以使用音频处理库将原始音频(单声道,采样位数为16bit)转换为 Base64 编码格式,Python示例如下:
| |
end_of_speech | Bool | 是 | 一个speech中的最后一次发送GenerateVideo消息时,需要将end_of_speech置为true,表示当前speech的数据已全部发送 | false |
event消息:SentenceStarted
当发送GenerateVideo消息并传入sentence_id
时,每句播报开始时,服务端会返回SentenceStarted
消息。此机制适用于字幕与音频在句子级别的对齐,表示数字人开始播报对应sentence_id
的句子。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "result-generated"
},
"payload": {
"output": {
"header": {
"name": "SentenceStarted"
},
"payload": {
"speech_id": "speech_id",
"sentence_id": "sentence_id"
}
}
}
}
字段 | 类型 | 描述 |
字段 | 类型 | 描述 |
speech_id | String | 当前speech的id,取决于发送GenerateVideo消息时传入的speech_id |
sentence_id | String | 当前开始播报的sentence_id,取决于发送GenerateVideo消息时传入的sentence_id |
action消息:TriggerHeartbeat
目前,数字人服务具备超时断开机制。如果客户端在1分钟
内未向数字人发送数据,WebSocket连接将自动断开。为避免这种情况,客户端可主动触发心跳机制。当服务端接收到心跳消息后,会返回一个AvatarHeartbeat消息,从而保持WebSocket连接的活跃状态。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"action": "continue-task"
},
"payload": {
"input": {
"header": {
"name": "TriggerHeartbeat"
},
"payload": {
}
}
}
}
event消息:AvatarHeartbeat
服务端会在以下三种场景中返回心跳消息(AvatarHeartbeat
):
持续播报 :当数字人基于客户端输入的音频持续生成播报画面时,服务端会以固定时间间隔(约5秒)返回心跳消息。
状态变更 :当客户端发送ChangeAvatarStatus消息时,服务端会返回心跳消息。
主动上报心跳 :当客户端发送TriggerHeartbeat消息时,服务端会响应并返回心跳消息。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "result-generated"
},
"payload": {
"output": {
"header": {
"name": "AvatarHeartbeat"
},
"payload": {
}
}
}
}
event消息:AvatarStatusChanged
数字人目前包含两种状态:听(LISTENING)
和说(SPEAKING)
。在交互过程中,AvatarStatusChanged 消息的 current_status
字段会根据数字人的状态变化进行更新:
当数字人开始当前轮次播报时,current_status 设置为 SPEAKING。
当数字人结束当前轮次播报时,current_status 设置为 LISTENING。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "result-generated"
},
"payload": {
"output": {
"header": {
"name": "AvatarStatusChanged"
},
"payload": {
"current_status": "LISTENING",
"speech_id": "speech_id"
}
}
}
}
字段 | 类型 | 描述 |
字段 | 类型 | 描述 |
current_status | String | 当前数字人的状态:LISTENING 或 SPEAKING |
speech_id | String | 当前speech_id,取决于发送GenerateVideo消息时传入的speech_id |
action消息:ChangeAvatarStatus
通过主动切换数字人状态,您可以打断数字人播报,使其从说(SPEAKING)切换至听(LISTENING)。
当数字人状态成功切换时,服务端会返回AvatarStatusChanged消息。 需要注意的是,如果数字人在收到打断消息时并未处于说状态(SPEAKING),则不会返回AvatarStatusChanged消息。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"action": "continue-task"
},
"payload": {
"input": {
"header": {
"name": "ChangeAvatarStatus"
},
"payload": {
"target_status": "LISTENING"
}
}
}
}
字段 | 类型 | 必选 | 描述 | 示例值 |
字段 | 类型 | 必选 | 描述 | 示例值 |
header | Object | 是 | action消息头 | 参考action消息数据格式 |
header.task_id | String | 是 | 当次任务ID,建议使用随机生成的32位UUID | 97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx |
header.action | String | 是 | 发送给服务端的事件类型 | 固定值为continue-task |
payload | Object | 是 | action消息体 | |
payload.input | Object | 是 | 请求体的输入内容 | |
payload.input.header | Object | 是 | 输入内容的请求头 | |
payload.input.header.name | String | 是 | 消息名称 | ChangeAvatarStatus |
payload.input.payload | Object | 是 | 发送给服务端的关键信息 | 见下 |
payload.input.payload参数
字段 | 类型 | 必选 | 描述 | 示例值 |
字段 | 类型 | 必选 | 描述 | 示例值 |
target_status | String | 是 | 数字人状态:
| LISTENING |
3. 关闭WebSocket连接
action消息:DestroyVideoSession
客户端发送销毁数字人Session消息,结束此次会话。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"action": "finish-task"
},
"payload": {
"input": {
"header": {
"name": "DestroyVideoSession"
}
}
}
}
event消息:VideoSessionDestroyed
数字人Session销毁完成,当客户端收到该消息后,可以断开WebSocket连接。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "task-finished"
},
"payload": {
"output": {
"header": {
"name": "VideoSessionDestroyed"
}
}
}
}
4. 处理任务失败
event消息:AvatarProcessError
当模型处理异常时,会返回错误消息AvatarProcessError并中断会话。此时,客户端需要关闭 WebSocket 连接。
您可以根据返回的 status_code
、status_name
和 message
查阅错误码,按要求处理。
{
"header": {
"task_id": "97d75ef6-8604-4b86-bac0-xxxxxxxxxxxx",
"event": "task-failed",
"status_code": "400",
"status_name": "InvalidParameter"
},
"payload": {
"output": {
"header": {
"name": "AvatarProcessError"
},
"payload": {
"message": "xxx"
}
}
}
}
测试示例
请参见前提条件将DASHSCOPE_API_KEY、RTC_APP_ID、RTC_APP_KEY配置到环境变量。
配置Python环境
推荐使用Python3.10及以上版本。
安装必要的依赖包。
pip install loguru numpy asyncio websockets==15.0.1
新建一个
demo.py
文件,并将以下 Python 代码复制到该文件中。
下载audio48k.wav到本地,视频文件位置与
demo.py
的文件目录结构如下:
avatar
├── demo.py # Python 示例代码
└── wav # 存放音频文件的文件夹
└── audio48k.wav # 示例音频文件
运行demo.py。
python demo.py
脚本运行后,日志中会打印一个 URL。在浏览器中打开该地址查看数字人播报的推流画面。如果遇到黑屏情况,刷新页面即可恢复正常。
推流画面展示如下所示。
错误码
接口状态(status_code) | 错误消息(message) | 说明 |
接口状态(status_code) | 错误消息(message) | 说明 |
400 | rtc_param is required | 需要客户端提供RTC入会参数 |
400 | avatar_id xxx invalid, download avatar resource failed | avatar_id不存在,请在公共形象库中选择某一形象 |
400 | sample_rate must be 16000, 24000, 32000, 48000 | 音频采样率设置错误 |
400 | Avatar receive message from client timeout, since last receive xxx | 客户端1分钟未发送消息,服务端会主动断开连接 |
下一步
本服务依赖视频直播云产品中的 RTC 能力,生成的音视频流将被推送到 RTC 中。如需获取音视频流,您需要接入 RTC 客户端,并开发拉流相关逻辑。
公共形象库
AvatarId
表示数字人形象ID。请复制 AvatarId 的值,如jiaoyue
,在上述WebSocket API 中使用。
AvatarId: taoji 名称:桃叽 | AvatarId: aria 名称:Aria | AvatarId: jiaoyue 名称:椒月 | AvatarId: shian 名称:时安 |
AvatarId: meike 名称:莓可 | AvatarId: yanqiu 名称:砚秋 | AvatarId: tangli 名称:棠梨 | AvatarId: xingyao 名称:星瑶 |
AvatarId: lengzhou 名称:棱舟 | AvatarId: mowen 名称:墨翁 |
常见问题
为什么运行demo.py后报错AccessDenied?
您没有开通数字人实时对话avatar-dialog模型,无权限访问。请在百炼模型广场申请使用该模型,申请通过后再重试。
报错信息如下:
{
"header": {
"task_id": "a9b6f562-af91-4076-9b37-xxxxxx",
"event": "task-failed",
"error_code": "AccessDenied",
"error_message": "Access denied.",
"attributes": {}
},
"payload": {}
}
- 本页导读 (1)
- 功能及模型介绍
- 前提条件
- WebSocket客户端与服务端交互流程
- WebSocket客户端实现流程
- 1. 建立WebSocket链接
- 2. 发送并监听消息
- 3. 关闭WebSocket连接
- 4. 处理任务失败
- 测试示例
- 错误码
- 下一步
- 公共形象库
- 常见问题