实时数字人交互协议详解
1. 简介
Nuwa 平台为数字人实时交互提供了一套开放接口协议,旨在允许开发者集成数字人能力。 该协议的核心分为两个协同工作的部分:
1.1 信令交互
客户端通过 WebSocket 与 Nuwa 平台建立信令通道。此通道负责管理会话与业务逻辑,主要功能包括:
- websocket 连接验证:验证数字人身份
- 媒体协商:协商音视频传输协议
- 语音/文本消息上报:发送用户输入
- 业务消息通知:接收数字人状态、响应文本等
1.2 音视频实时流
客户端通过 WebRTC 或 RTMP 协议,从协商好的地址拉取数字人的实时音视频流。
示例
注意
如果只想用ASR能力参考: Nuwa ASR 实时语音识别协议
2. 开发前须知
- 获取 API Key:
在开始之前,您需要在 NuwaAI平台完成以下步骤:
- 注册一个 NuwaAI账号,并登录
- 登录后,记录下用户ID
- 创建一个数字人,并记录下数字人 ID
- 创建一个API Key,并记录下 API Key
获取 Access Token:
- 实时数字人交互协议需要通过鉴权接口协议获取Access Token验证用户权限,确保交互安全
实时数字人交互协议交互流程:
- 使用WebRTC协议的交互流程
sequenceDiagram
participant 客户端 as 客户端
participant 平台 as Nuwa 平台
participant 服务 as Nuwa 数字人服务
客户端->>平台: 1. 创建 WebSocket 连接
平台-->>客户端: 2. 响应 avator-connect (鉴权结果)
客户端->>平台: 3. playStream (携带本地 SDP)
平台-->>客户端: 4. 响应 avator-play (返回远端 SDP)
客户端->>客户端: 5. 解析 SDP,初始化接收端 WebRTC 收流处理
服务-->>客户端: 6. 推送音视频流
服务-->>客户端: 7. avator-playstop (开场白结束)
客户端-->>平台: 8. 16k 单通道语音数据
平台-->>服务: 转发语音数据
客户端-->>平台: 9. 发送对话消息(可选)
平台-->>客户端: 10. chat_begin (对话开始)
平台-->>客户端: 11. chat (对话进行中)
平台-->>客户端: 12. chat_end (对话结束)
服务-->>客户端: 13. avator-text (数字人回复文本)
服务-->>客户端: 14. avator-text (后续文本)
服务-->>客户端: 15. avator-playstop (回复结束)
- 使用RTMP协议的交互流程
sequenceDiagram
participant C as 客户端 📱
participant P as Nuwa 平台 ☁️
participant S as Nuwa 数字人服务 🤖
participant R as RTMP接收端 📡
%% 1. 连接与鉴权
C->>P: 1. 创建 websocket 连接
P-->>C: 2. 响应 avator-connet (鉴权结果)
%% 2. 启动 RTMP 推流
C->>P: 3. playRtmp (携带 rmtp_url)
P->>S: 4. playRtmp (转发 rmtp_url)
%% 3. 平台通知客户端可以播放
S-->>P: 5. 推流准备就绪 (内部状态)
P-->>C: 6. 响应 avator-play (通知播放)
%% 4. 数字人服务推流至接收端
S->>R: 7. Rtmp 推流 (视频流)
%% 5. 开场白结束通知
S-->>P: 8. avator-playstop (开场白结束)
P-->>C: 9. 转发 avator-playstop
%% 6. 语音输入阶段
C->>P: 10. 16k 单通道语音数据
P->>S: 11. 转发语音数据
%% 7. 对话消息通知(可选)
P-->>C: 12. chat_begin (对话开始)
P-->>C: 13. chat (对话进行中)
P-->>C: 14. chat_end (对话结束)
%% 8. 回复
S-->>P: 15. avator-text (数字人回复)
P-->>C: 16. 转发 avator-text
S-->>P: 17. avator-text (后续文本)
P-->>C: 18. 转发 avator-text
S-->>P: 19. avator-playstop (回复结束)
P-->>C: 20. 转发 avator-playstop
3. websocket信令
3.1 websocket 连接验证
创建 WebSocket 连接
wss://wsapi.nuwaai.com/websk/humanAgent/humanchat/{protocol}/{avatorid}/{bizid}?access_token={token}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| protocol | String | 否 | 流传输协议:webrtc(默认)rtmp |
| avatorid | String | 是 | 数字人角色 |
| bizid | String | 是 | 业务 id (humanctrl场景中启用asr则为asr,其他场景为0) |
| token | String | 是 | 鉴权后获得的token |
注意
如果是仅控制数字人(播报、动作、表情等)的业务,使用请求:
wss://wsapi.nuwaai.com/websk/humanAgent/humanctrl/{protocol}/{avatorid}/{bizid}?access_token={token}
3.1.2 等待验证回应
协议: JSON 文本消息
{
"code": {code},
"msg": {message},
"type": "avator-connect",
"data": {
"question": {question},
"sessionid": {sessionid}
}
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| code | int | 是 | 响应码:200 为 ok,其他为错误,具体错误 message |
| message | String | 否 | 当响应码为 200 时返回。具体错误原因 |
| type | String | 是 | 消息类型:avator-connect |
| data | Object | 是 | 消息体 |
| data.question | Array | 是 | 3 个提示问题: ["提示1", "提示2", "提示3"] |
| data.sessionid | String | 是 | 会话 id |
// WebSocket 连接示例
const protocol = 'webrtc'; // 或 'rtmp'
const avatorId = 'your-avator-id';
const bizId = '0'; // 或 'asr'(humanctrl场景中启用asr)
const token = 'your-access-token';
const wsUrl = `wss://wsapi.nuwaai.com/websk/humanAgent/humanchat/${protocol}/${avatorId}/${bizId}?access_token=${token}`;
const ws = new WebSocket(wsUrl);
// 连接建立
ws.onopen = function() {
console.log('WebSocket 连接已建立');
};
// 接收消息
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('收到消息:', message);
// 处理连接验证响应
if (message.type === 'avator-connect') {
if (message.code === 200) {
console.log('连接验证成功,会话ID:', message.data.sessionid);
console.log('提示问题:', message.data.question);
// 连接验证成功后,可以进行下一步操作
} else {
console.error('连接验证失败:', message.msg);
}
}
};
// 连接关闭
ws.onclose = function() {
console.log('WebSocket 连接已关闭');
};
// 连接错误
ws.onerror = function(error) {
console.error('WebSocket 连接错误:', error);
};
3.2 媒体协商
通过 WebSocket 发送媒体协商请求。协商协议支持 WebRTC 和 RTMP,WebSocket 的连接协议参数和协商协议需一致。
3.2.1 WebRTC 协议
发送媒体协商请求
协议: JSON 文本消息
{
"type": "playStream",
"data": {
"sdp": {sdp}
}
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| type | String | 是 | 消息类型:playStream |
| data | Object | 是 | 消息体 |
| data.sdp | String | 是 | 本地 sdp |
等待协商回应
协议: JSON 文本消息
{
"code": {code},
"msg": {message},
"type": "avator-play",
"data": {
"sdp": {sdp}
}
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| code | int | 是 | 响应码:200 为 ok,其他为错误,具体错误 message |
| message | String | 否 | 当响应码为 200 时返回。具体错误原因 |
| type | String | 是 | 消息类型:avator-play |
| data | Object | 是 | 消息体 |
| data.sdp | sdp | 是 | 远端 sdp 信息 |
// 发送 WebRTC 媒体协商请求
function sendPlayStream(ws, localSdp) {
const playStreamMessage = {
type: 'playStream',
data: {
sdp: localSdp
}
};
ws.send(JSON.stringify(playStreamMessage));
console.log('发送 playStream 请求:', playStreamMessage);
}
// 处理 WebRTC 协商回应
function handleAvatorPlay(message) {
if (message.code === 200) {
const remoteSdp = message.data.sdp;
console.log('收到 avator-play 响应,远端 SDP:', remoteSdp);
// 使用远端 SDP 建立 WebRTC 连接
// 这里需要根据你的 WebRTC 实现进行处理
// 例如:
// pc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: remoteSdp }));
} else {
console.error('媒体协商失败:', message.msg);
}
}
// 示例:在 WebSocket 消息处理中添加
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('收到消息:', message);
if (message.type === 'avator-connect') {
// 处理连接验证响应...
} else if (message.type === 'avator-play') {
// 处理媒体协商响应
handleAvatorPlay(message);
}
};
3.2.2 RTMP 协议
发送媒体协商请求
协议: JSON 文本消息
{
"type": "playRtmp",
"data": {
"url": {url}
}
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| type | String | 是 | 消息类型:playRtmp |
| data | Object | 是 | 消息体 |
| data.url | String | 是 | 数字人推送地址 |
等待协商回应
协议: JSON 文本消息
{
"code": {code},
"msg": {message},
"type": "avator-play"
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| code | int | 是 | 响应码:200 为 ok,其他为错误,具体错误 message |
| message | String | 否 | 当响应码为 200 时返回。具体错误原因 |
| type | String | 是 | 消息类型:avator-play |
// 发送 RTMP 媒体协商请求
function sendPlayRtmp(ws, rtmpUrl) {
const playRtmpMessage = {
type: 'playRtmp',
data: {
url: rtmpUrl
}
};
ws.send(JSON.stringify(playRtmpMessage));
console.log('发送 playRtmp 请求:', playRtmpMessage);
}
// 处理 RTMP 协商回应
function handleAvatorPlayRtmp(message) {
if (message.code === 200) {
console.log('RTMP 媒体协商成功');
// 开始通过 RTMP 协议接收音视频流
// 这里需要根据你的 RTMP 播放器实现进行处理
// 例如:使用 video.js 或其他播放器加载 RTMP 流
} else {
console.error('RTMP 媒体协商失败:', message.msg);
}
}
// 示例:在 WebSocket 消息处理中添加
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('收到消息:', message);
if (message.type === 'avator-connect') {
// 处理连接验证响应...
} else if (message.type === 'avator-play') {
// 处理媒体协商响应
handleAvatorPlayRtmp(message);
}
};
3.3 发送对话消息
语音对话消息,直接发送 16k 单通道 PCM 数据,按 60ms 一个 chunk 发送。文本消息按照 JSON 文本消息格式发送。
3.3.1 文本对话协议
协议: JSON 文本消息
{
"type": "agent",
"data": {
"content": {text}
}
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| type | String | 是 | 消息类型:agent |
| data | Object | 是 | 消息体 |
| data.content | String | 是 | 对话内容 |
// 发送文本对话消息
function sendTextMessage(ws, text) {
const textMessage = {
type: 'agent',
data: {
content: text
}
};
ws.send(JSON.stringify(textMessage));
console.log('发送文本消息:', textMessage);
}
// 示例:发送文本消息
// sendTextMessage(ws, '你好,数字人!');
// 处理对话相关的响应
function handleChatMessages(message) {
switch (message.type) {
case 'chat_begin':
console.log('对话开始');
break;
case 'chat':
console.log('大模型回应:', message.data.content);
break;
case 'chat_end':
console.log('对话结束');
break;
case 'avator-text':
console.log('数字人回复:', message.data.text);
break;
case 'avator-playstop':
console.log('数字人回复结束');
break;
}
}
// 示例:在 WebSocket 消息处理中添加
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('收到消息:', message);
if (message.type === 'avator-connect') {
// 处理连接验证响应...
} else if (message.type === 'avator-play') {
// 处理媒体协商响应...
} else if (['chat_begin', 'chat', 'chat_end', 'avator-text', 'avator-playstop'].includes(message.type)) {
// 处理对话相关的响应
handleChatMessages(message);
}
};
3.3.2 数字人控制消息
仅 humanctrl 连接中有效。
协议: JSON 文本消息
{
"type": "avatar",
"data": {
"content": {text},
"audio": {
"segment": {segment},
"speech": {speech},
"music": {music}
}
}
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| type | String | 是 | 消息类型:avatar |
| data | Object | 是 | 消息体 |
| data.content | String | 是 | 数字人的播报内容 |
| data.audio | Object | 否 | 音频数据:16k 单通道 PCM |
| data.audio.segment | Number | 是 | 音频分片索引,-1 表示最后分片,0为第一分片,以此类推 |
| data.audio.speech | String | 是 | 人声语音数据,base64 编码, 分片音频<=10K |
| data.audio.music | String | 是 | 人声+音乐的混音数据,base64 编码, 分片音频<=10K |
注意
当前仅支持16k 单通道 PCM 音频数据,base64 编码。
当传入音频数据时,数字人会根据speech音频数驱动嘴型,music音频数做播报。
当不传入音频数据时,数字人会根据content转换为音频内容做播报。
// 发送数字人控制消息(仅在 humanctrl 连接中有效)
function sendAvatarControlMessage(ws, content) {
const controlMessage = {
type: 'avatar',
data: {
content: content
}
};
ws.send(JSON.stringify(controlMessage));
console.log('发送数字人控制消息:', controlMessage);
}
// 示例:发送数字人控制消息
// sendAvatarControlMessage(ws, '欢迎使用 Nuwa 数字人服务!');
// 处理数字人控制相关的响应
function handleAvatarControlResponse(message) {
if (message.type === 'avator-ctrl') {
console.log('数字人控制消息:', message.data);
} else if (message.type === 'avator-playstop') {
console.log('数字人回复结束');
}
}
// 示例:在 WebSocket 消息处理中添加
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('收到消息:', message);
if (message.type === 'avator-connect') {
// 处理连接验证响应...
} else if (message.type === 'avator-ctrl' || message.type === 'avator-playstop') {
// 处理数字人控制相关的响应
handleAvatarControlResponse(message);
}
};
3.3.2 数字人打断消息
仅 humanctrl 连接中有效。打断数字人说话
协议: JSON 文本消息
{
"type": "interrupt_chat"
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| type | String | 是 | 消息类型:interrupt_chat |
// 发送数字人打断消息(仅在 humanctrl 连接中有效)
function sendInterruptChatMessage(ws) {
const interrupt_chat_message = {
type: 'interrupt_chat'
};
ws.send(JSON.stringify(interrupt_chat_message));
console.log('发送数字人打断消息:', interrupt_chat_message);
}
3.4 业务消息通知
数字人连接建立后,数字人服务会通过 WebSocket 发送业务通知。
协议: JSON 文本消息
{
"code": {code},
"msg": {message},
"type": {type},
"data": {data}
}
参数说明
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| code | int | 是 | 响应码:200 为 ok,其他为错误,具体错误 message |
| message | String | 否 | 当响应码为 200 时返回。具体错误原因 |
| type | String | 是 | 消息类型 |
| data | json | 否 | 由 type 值决定 data 内容 |
消息类型说明
| type | 说明 |
|---|---|
| avator-load | 数字人加载通知 |
| avator-connect | 数字人连接通知(参考鉴权) |
| avator-play | 数字人 sdp 通知(参考接收 sdp 信息) |
| avator-text | 数字人当前说话文本(字幕) |
| avator-playstop | 数字人回应结束 |
| avator-ctrl | 数字人控制消息 |
| asr-text | 用户说话实时文本(全量) |
| asr-commit | 用户说话提交给大模型的文本 |
| chat_begin | 大模型的回应开始 |
| chat | 大模型回应文本 |
| chat_end | 大模型回应结束 |
data 值描述
type: avator-text
{
"text": {text}
}
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| text | String | 是 | 文本内容 |
type: asr-text / asr-commit / chat
{
"content": {text}
}
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| text | String | 是 | 文本内容 |
type: avator-ctrl
{
"cmd": {cmd},
"action": {action},
"src": {src}
}
| 参数名称 | 类型 | 是否必填 | 参数说明 |
|---|---|---|---|
| cmd | String | 是 | 控制操作:picture, video |
| action | String | 是 | 动作类型:ppt |
| src | String | 是 | picture 或者 video 的路径 |
4. 音视频实时流协议
4.1 WebRTC 实时流协议
WebRTC (Web Real-Time Communication) 是一种支持网页浏览器进行实时语音、视频通话的开源项目,在 Nuwa 平台中用于拉取数字人的实时音视频流。
4.1.1 协议概述
- 传输方式:基于 UDP 的点对点传输
- 媒体格式:支持 H.264 视频编码和 Opus 音频编码
- 延迟:低延迟,适合实时交互场景
- 安全性:内置加密机制
4.1.2 连接流程
- 建立 WebSocket 连接:使用配置信息构建 WebSocket URL 并建立连接
- 接收连接验证:接收
avator-connect消息,获取会话 ID - 开始媒体协商:创建 RTCPeerConnection,添加音频和视频接收器
- 发送协商请求:发送
playStream请求,携带本地 SDP - 处理协商响应:接收
avator-play消息,获取远端 SDP - 设置远程描述:使用远端 SDP 设置 RTCPeerConnection 的远程描述
- 接收音视频流:通过
ontrack事件接收数字人的音视频流
sequenceDiagram
participant 客户端 as 客户端
participant 平台 as Nuwa 平台
participant 服务 as Nuwa 数字人服务
客户端->>平台: 1. 建立 WebSocket 连接
平台-->>客户端: 2. 响应 avator-connect (返回会话 ID)
客户端->>客户端: 3. 创建 RTCPeerConnection
客户端->>客户端: 4. 添加音频和视频接收器
客户端->>客户端: 5. createOffer 获取本地 SDP
客户端->>客户端: 6. setLocalDescription 设置本地 SDP
客户端->>平台: 7. 发送 playStream (携带本地 SDP)
平台->>服务: 8. 转发 playStream 请求
服务-->>平台: 9. 返回远端 SDP
平台-->>客户端: 10. 响应 avator-play (返回远端 SDP)
客户端->>客户端: 11. setRemoteDescription 设置远程 SDP
服务-->>客户端: 12. 推送音视频流
客户端->>客户端: 13. 通过 ontrack 事件接收流
4.1.3 WebRTC 实现示例
// WebRTC 连接示例
let peerConnection = null;
let peerStream = null;
// 开始媒体协商
function startMediaNegotiation(socket, videoElementId) {
// 创建 RTCPeerConnection
peerConnection = new RTCPeerConnection(null);
// 创建 MediaStream
peerStream = new MediaStream();
// 设置视频元素的源
const video = document.getElementById(videoElementId);
video.srcObject = peerStream;
video.onplay = function() {
video.muted = false;
};
// 处理音视频流
peerConnection.ontrack = function(event) {
console.log('收到视频流: ' + event.streams.length + ' 个流');
peerStream.addTrack(event.track);
};
// 添加音频和视频接收器
peerConnection.addTransceiver('audio', { direction: 'recvonly' });
peerConnection.addTransceiver('video', { direction: 'recvonly' });
// 创建并发送 SDP
peerConnection.createOffer().then(offer => {
return peerConnection.setLocalDescription(offer);
}).then(() => {
const message = {
type: 'playStream',
data: {
sdp: peerConnection.localDescription.sdp
}
};
socket.send(JSON.stringify(message));
console.log('发送 WebRTC 协商请求');
}).catch(error => {
console.error('WebRTC 协商错误: ' + error);
});
}
// 处理 SDP 应答
function handleSdpAnswer(sdp) {
if (peerConnection) {
const remoteDescription = new RTCSessionDescription({ type: 'answer', sdp: sdp });
peerConnection.setRemoteDescription(remoteDescription).then(() => {
console.log('WebRTC 协商成功');
}).catch(error => {
console.error('WebRTC 协商应答错误: ' + error);
});
}
}
// 断开 WebRTC 连接
function disconnectWebRTC() {
if (peerConnection) {
peerConnection.close();
peerConnection = null;
}
if (peerStream) {
peerStream.getTracks().forEach(track => track.stop());
peerStream = null;
}
console.log('WebRTC 连接已断开');
}
// 示例:在 WebSocket 消息处理中添加
function handleWebSocketMessage(message, socket) {
switch (message.type) {
case 'avator-connect':
if (message.code === 200) {
console.log('鉴权成功,会话 ID: ' + message.data.sessionid);
// 开始媒体协商
startMediaNegotiation(socket, 'remote-video');
} else {
console.log('鉴权失败: ' + message.msg);
}
break;
case 'avator-play':
if (message.code === 200 && message.data && message.data.sdp) {
// 处理 SDP 应答
handleSdpAnswer(message.data.sdp);
}
break;
// 其他消息处理...
}
}
4.1.4 错误处理
| 错误类型 | 可能原因 | 解决方案 |
|---|---|---|
| ICE 连接失败 | 网络 NAT 穿透失败 | 配置 TURN 服务器 |
| 媒体流无法播放 | SDP 解析错误 | 检查 SDP 格式是否正确 |
| 音视频卡顿 | 网络带宽不足 | 调整视频分辨率或比特率 |
| 连接断开 | 网络不稳定 | 实现重连机制 |
4.2 RTMP 实时流协议
RTMP (Real-Time Messaging Protocol) 是一种用于音视频流传输的协议,在 Nuwa 平台中用于拉取数字人的实时音视频流。
4.2.1 协议概述
- 传输方式:基于 TCP 的流媒体协议
- 媒体格式:支持 H.264 视频编码和 AAC 音频编码
- 延迟:中等延迟,适合直播场景
- 兼容性:广泛支持各种播放器
4.2.2 连接流程
- 获取推流地址:客户端提供 RTMP 推流地址
- 发送协商请求:通过 WebSocket 发送
playRtmp请求 - 等待响应:接收平台返回的
avator-play响应 - 拉取流:使用 RTMP 播放器从指定地址拉取音视频流
sequenceDiagram
participant 客户端 as 客户端
participant 平台 as Nuwa 平台
participant 服务 as Nuwa 数字人服务
participant 播放器 as RTMP 播放器
客户端->>平台: 1. playRtmp (携带 rmtp_url)
平台->>服务: 2. 转发 playRtmp 请求
服务-->>平台: 3. 推流准备就绪
平台-->>客户端: 4. 响应 avator-play (通知播放)
服务->>播放器: 5. RTMP 推流 (视频流)
播放器-->>客户端: 6. 显示音视频
4.2.3 RTMP 实现示例
// RTMP 播放器示例(使用 video.js)
// 首先引入 video.js 和 videojs-flash 插件
// <script src="https://cdn.jsdelivr.net/npm/video.js@7.15.4/dist/video.min.js"></script>
// <script src="https://cdn.jsdelivr.net/npm/videojs-flash@2.2.1/dist/videojs-flash.min.js"></script>
// 初始化播放器
const player = videojs('remote-video', {
techOrder: ['html5', 'flash'],
autoplay: true,
controls: true,
sources: [
{
src: 'rtmp://your-rtmp-server/live/stream',
type: 'rtmp/flv'
}
]
});
// 播放器事件监听
player.on('play', function() {
console.log('播放器开始播放');
});
player.on('error', function() {
console.error('播放器错误:', player.error());
});
player.on('ended', function() {
console.log('播放结束');
});
// 发送 playRtmp 请求
function sendPlayRtmp(ws, rtmpUrl) {
const playRtmpMessage = {
type: 'playRtmp',
data: {
url: rtmpUrl
}
};
ws.send(JSON.stringify(playRtmpMessage));
console.log('发送 playRtmp 请求:', playRtmpMessage);
}
// 处理 avator-play 响应
function handleAvatorPlay(message) {
if (message.code === 200) {
console.log('RTMP 媒体协商成功,开始播放');
// 播放器已经配置好,会自动开始播放
} else {
console.error('RTMP 媒体协商失败:', message.msg);
}
}
4.2.4 错误处理
| 错误类型 | 可能原因 | 解决方案 |
|---|---|---|
| 连接失败 | RTMP 服务器地址错误 | 检查 RTMP 地址是否正确 |
| 流无法播放 | 推流未开始 | 等待数字人服务准备就绪 |
| 音视频卡顿 | 网络带宽不足 | 调整视频分辨率或使用更稳定的网络 |
| 播放器错误 | 播放器不支持 RTMP | 使用支持 RTMP 的播放器,如 video.js + flash 插件 |
5. 最佳实践
5.1 连接管理
- 建议使用自动重连机制,当 WebSocket 连接断开时自动重新连接
- 设置合理的超时时间,避免长时间等待
- 做好连接状态管理,确保用户了解当前连接状态
5.2 性能优化
- 语音数据发送时,建议按照 100ms/200ms 一个 chunk 进行分片
5.3 错误处理
- 建议实现完善的错误回调机制,及时处理各种异常情况
- 对于可恢复的错误(如网络波动),实现自动重试机制
- 记录错误日志,便于问题排查
5.4 安全性
- 妥善保管 API Key,不要暴露在客户端代码中
- 使用 HTTPS 和 WSS 加密传输,保护数据安全
6. 常见问题
6.1 连接相关
Q: WebSocket 连接失败怎么办?
A: 请检查以下几点:
- 确认 access_token 是否有效
- 检查网络连接是否正常
- 确认数字人 ID 是否正确
- 查看错误码信息,定位具体原因
Q: 连接成功后没有收到鉴权响应?
A: 可能是网络延迟或数字人模型加载处理较慢,建议增加超时等待时间,同时检查 WebSocket 连接状态。
6.2 音视频相关
Q: 音视频卡顿怎么办?
A: 建议检查以下几点:
- 网络带宽是否充足
- 客户端设备性能是否满足当前数字人的H264解码能力要求
- 检查是否有其他应用占用网络带宽
Q: 无法拉取音视频流?
A: 请确认:
- 媒体协商是否成功
- SDP 解析是否正确
- 防火墙是否阻止了相关端口
6.3 消息相关
Q: 发送语音数据后没有响应?
A: 请检查:
- 语音数据格式是否为 16k 单通道 PCM
- 数据分片大小是否正确(建议 100ms/200ms 一个 chunk)
Q: 收到的文本消息是乱码?
A: 请确保使用 UTF-8 编码解析消息内容。