You need to enable JavaScript to run this app.
文档中心
图像生成大模型

图像生成大模型

复制全文
下载 pdf
实时互动数字人API
实时互动数字人API-接口文档
复制全文
下载 pdf
实时互动数字人API-接口文档
接口地址​
​
协议名称​
地址​
WebSocket​
wss://openspeech.bytedance.com/virtual_human/avatar_live/live​
​
协议帧汇总说明​
客户端 → 服务端​
​
Header​
Message Type​
Body​
用途​
|CTL|00|​
Text​
JSON​
开播或重连初始化信息​
|CTL|01|​
Text​
N/A​
正常结束直播​
|CTL|12|​
Text​
N/A​
流式音频驱动结束命令​
|DAT|02|​
Binary​
Binary PCM Data​
流式音频驱动​
|DAT|04|​
Text​
JSON​
结构化流式音频驱动​
|DAT|05|​
Text​
JSON​
Prompt 驱动动作/表情​
​
服务端 → 客户端​
​
Header​
Message Type​
Body​
用途​
|MSG|00|​
Text​
JSON​
确认建立连接,状态码详见下述状态码说明​
|MSG|01|​
Text​
JSON​
服务异常通知,状态码详见下述状态码说明​
|MSG|02|​
Text​
N/A​
心跳​
|DAT|02|​
Text​
JSON​
返回合成状态​
​
交互阶段说明​
开播或重连初始化​
客户端请求​
  • Message Type:Text​
  • Header:|CTL|00|​
  • Body:参数如下​
​
参数名称​
类型​
层级​
必需​
说明​
live​
object​
1​
必需​
直播会话相关配置​
live_id​
string​
​
2​
必需​
​
直播 ID​
限制说明:​
  • 需要保证唯一,一个 live_id 只能开播一次,不能重复使用,建议采用:{公司名_直播名}。​
keep_alive_duration​
​
int​
​
2​
​
非必需​
​
保活时长(单位:秒),websocket 异常断连后服务对此路并发的保活时长,保活期间端侧可进行重连,保活到期后并发会释放​
  • < 0:无限保活​
  • = 0:不保活​
  • > 0:具体保活时长​
默认值:0​
auth​
object​
1​
必需​
鉴权相关配置​
type​
int​
2​
必需​
鉴权类型​
固定值:1​
appid​
string​
2​
必需​
火山引擎账号ID(可在火山引擎官网右上角查看)​
token​
string​
2​
必需​
火山预签名URL,签名方法参考 代码示例-获取火山预签名URL​
token 可设置过期时间,详见代码示例中的 expires 参数​
avatar​
object​
1​
必需​
数字人相关信息​
avatar_type​
​
string​
2​
必需​
数字人类型​
固定值:flowact-r1​
input_mode​
string​
2​
必需​
驱动模式​
固定值:audio​
role​
string​
2​
必需​
角色形象图URL(需公网可访问)​
flowact_r1​
object​
2​
非必需​
flowact-r1参数​
text_list​
array of string​
3​
非必需​
动作指令列表​
开播后,数字人将按列表顺序循环执行预设动作。当未设置该参数时,将启用默认值,默认值请参考表格下方的 动作指令默认列表。​
支持用户自定义该动作指令列表,动作指令描述可参考默认值中的描述。​
streaming​
object​
1​
必需​
推流相关配置​
type​
string​
2​
必需​
推流方式​
固定值:bytertc​
rtc_app_id​
string​
2​
必需​
ByteRTC 应用 ID(AppId)​
可前往 ByteRTC控制台 创建应用并获取​​​
rtc_room_id​
​
string​
2​
必需​
ByteRTC 房间 ID(RoomId)​
限制说明:​
  • 建议每次连接都使用唯一的 rtc_room_id​
rtc_uid​
​
string​
2​
必需​
ByteRTC 推流用户 ID(UserId)​
限制说明:​
rtc_token​
string​
2​
必需​
​
ByteRTC 推流 Token​
测试阶段可前往 ByteRTC控制台 创建并获取临时 Token​​​
正式上线前请切换Token为正式Token Token详细说明文档​
​
动作指令默认列表​
​
Body参数示例:​
​
{​
"live": {​
"live_id": "company_livename",​
"keep_alive_duration": 0​
},​
"auth": {​
"type": 1,​
"appid": "填写火山引擎账号ID",​
"token": "https://billing.volcengineapi.com/?X-Algorithm=..."​
},​
"avatar": {​
"avatar_type": "flowact-r1",​
"input_mode": "audio",​
"role": "https://xxxx"​
},​
"streaming": {​
"type": "bytertc",​
"rtc_app_id": "xxx",​
"rtc_room_id": "xxx",​
"rtc_uid": "xxx",​
"rtc_token": "xxx"​
}​
}​
​
服务端响应​
  • Message Type:Text​
  • Header:|MSG|00|​
  • Body:参数如下​
​
参数名称​
类型​
层级​
必需​
说明​
code​
int​
1​
必需​
​
状态码​
1000:表示成功​
其它为错误码,详见下述状态码说明​
message​
string​
1​
必需​
状态信息​
code=1000 时为 success​
其它时为错误信息​
data​
object​
1​
非必需​
事件数据​
​
Body参数示例:​
​
{​
"code": 1000,​
"message": "success",​
"data": {​
"rtc": {​
"app_id": "xxx",​
"room_id": "xxx",​
"inner_uid": "xxx",​
"remote_uid": "",​
"token": ""​
}​
}​
}​
​
播放状态反馈​
返回合成状态​
  • Message Type:Text​
  • Header:|DAT|02|​
  • Body:参数如下​
​
参数名称​
类型​
层级​
必需​
说明​
type​
string​
1​
必需​
事件类型,常用值:​
  • voice_start:播报开始​
  • voice_continue:播报中​
  • voice_end:播报停止​
data​
object​
1​
非必需​
事件数据​
extra_data​
string​
2​
非必需​
  • 当对应帧有 extra_data 时必需​
自定义数据​
若驱动音频带有 extra_data,会在合成并推流到对应帧时将 extra_data 放入某个事件内返回​
​
Body参数示例:​
​
{​
"type": "voice_start",​
"data": {},​
"extra_data": ""​
}​
​
实时驱动​
音频驱动​
说明​
  1. 流式音频驱动、结构化流式音频驱动结束时需发送结束指令;​
  1. 在进行流式音频驱动 或 结构化流式音频驱动的同时,可以进行Prompt动作指令驱动;​
流式音频驱动​
  • Message Type:Binary​
  • Header:|DAT|02|​
  • Body:二进制音频数据(PCM),16kHz 单通道 2字节深度,长度不小于 40ms(1280 字节,一视频帧对应的音频长度)​
结构化流式音频驱动​
  • Message Type:Text​
  • Header:|DAT|04|​
  • Body:参数如下​
​
参数名称​
类型​
层级​
必需​
备注​
audio​
​
string​
1​
必需​
​
base64 编码的音频数据​
音频应为 16kHz 单通道 2字节深度,长度不小于 40ms(1280 字节,一视频帧对应的音频长度)​
​
流式音频驱动结束命令​
  • Message Type:Text​
  • Header:|CTL|12|​
Prompt驱动​
  • Message Type:Text​
  • Header:|DAT|05|​
  • Body:参数如下​
​
参数名称​
类型​
层级​
必需​
备注​
prompt​
​
string​
1​
必需​
​
驱动提示词​
通过 prompt 文本驱动数字人展示高表现力的动作、表情​
提示词详见下表​
​
​
提示词场景类型​
提示词​
聆听(不说话)​
聆听(不说话)- 自然呼吸​
​
聆听(不说话)- 小动作 - 整理头发​
​
聆听(不说话)- 开心​
​
聆听(不说话)- 惊讶​
开场​
开场 - 招手​
​
开场 - 双手合十​
​
开场 - 整理仪态​
感谢礼物与打赏​
感谢礼物与打赏 - 双手并拢比心​
​
感谢礼物与打赏 - 起立致敬​
​
感谢礼物与打赏 - 双手捂嘴感动​
​
感谢礼物与打赏 - 单手竖起大拇指​
​
感谢礼物与打赏 - 双手竖起大拇指​
引导关注与点赞​
引导关注与点赞 - 手指上方引导​
​
引导关注与点赞 - 手指下方引导​
​
引导关注与点赞 - 比 “1”​
情绪调节与氛围​
情绪调节与氛围 - 比耶(V 手势)​
​
情绪调节与氛围 - 单手托下巴​
​
情绪调节与氛围 - 捂嘴笑​
​
情绪调节与氛围 - 嘟嘟嘴​
​
情绪调节与氛围 - 凑近镜头​
​
情绪调节与氛围 - 摸摸头​
​
情绪调节与氛围 - 比比嘘​
​
情绪调节与氛围 - 隔空 “飞吻” 气流​
​
情绪调节与氛围 - 开心笑​
​
情绪调节与氛围 - 整理衣服​
​
情绪调节与氛围 - 双手欢呼​
​
Body参数示例:​
​
{​
"prompt": "开场 - 招手"​
}​
​
关播​
客户端请求​
  • Message Type:Text​
  • Header:|CTL|01|​
服务收到结束直播消息后会以 1000 作为 code 关闭 websocket 连接。​
异常通知​
服务异常通知​
  • Message Type:Text​
  • Header:|MSG|01|​
  • Body:参数如下​
​
参数名称​
类型​
层级​
必需​
备注​
code​
int​
1​
必需​
错误码​
详见下述状态码说明​
message​
string​
1​
必需​
错误信息​
​
服务发送异常通知后会以 4999 作为 code 关闭 websocket 连接。​
Body参数示例:​
​
{​
"code": 4002,​
"message": "failed to load or create engine: concurrency has no quota"​
}​
​
心跳​
服务端维持心跳​
通过 websocket 自带的 Ping/Pong 机制实现心跳,服务端每5s发送一次 Ping 包,客户端收到 Ping 包后应立即返回 Pong 包进行确认。服务端和客户端应自行对心跳消息进行超时处理(如超过3倍的心跳间隔判定为连接异常)。​
鉴于前端无法收发 websocket 自带的 Ping/Pong 消息,服务端会同时每5s发一包以下格式的消息:​
  • Message Type:Text​
  • Header:|MSG|02|​
状态码说明​
​
错误方​
状态码​
含义​
原因列举​
无​
1000​
成功​
  • 初始化信息填写合理,正常开播​
Client​
4000​
​
请求错误​
  • 请求解析失败​
  • 未填写必填字段​
​
4001​
鉴权错误​
  • 鉴权未通过​
​
4002​
并发超限​
  • 请求并发超出了购买的并发额度​
​
4003​
连接过多​
  • 同时有多个连接控制同一直播​
​
4004​
Live ID 重复​
  • 请求开播的 Live ID 已在使用中​
​
4006​
直播不存在​
  • 进行其它操作时直播不在开播中​
Server​
5000​
​
Live 服务内部错误​
​
  • 直播初始化报错​
  • 连接上游服务失败​
​
5001​
Avatar 服务内部错误​
  • 数字人服务初始化失败​
​
5002​
服务器忙​
  • 没有可用实例​
​
5100​
重连 Avatar 原子服务​
  • Avatar 原子服务实例迁移导致的重连,会使推理、画面有短暂中断​
​
代码示例​
获取火山预签名URL​
​
​
API交互示例​
​
import base64​
import json​
import logging​
import traceback​
import uuid​
import websocket​
import threading​
import time​
import wave​
import os​
​
from typing import Iterator, Optional​
​
​
logging.basicConfig(​
format="%(asctime)s %(levelname)s %(filename)s:%(funcName)s:%(lineno)s: %(message)s",​
level=logging.INFO,​
)​
​
# client side header​
StartLiveHeader = "|CTL|00|" # 开播或重连初始化信息​
CloseLiveHeader = "|CTL|01|" # 正常结束直播​
EndStreamingAudioHeader = "|CTL|12|" # 流式音频驱动结束命令​
StreamingAudioHeader = "|DAT|02|" # 流式音频驱动​
StreamingstructurationAudioHeader = "|DAT|04|" # 结构化流式音频驱动​
PromptHeader = "|DAT|05|" # Prompt 驱动​
​
# server side header​
StartLiveAckHeader = "|MSG|00|" # 确认建立连接​
PlayStatusHeader = "|DAT|02|" # 返回合成状态​
​
PCM_BYTES_PER_SECOND = 16000 * 2 # 1s PCM数据 (16kHz, 1 channel, 16 bits)​
MIN_PCM_CHUNK_BYTES = 1280 # 40ms PCM,要求单包不小于 1280 字节​
FIXED_ACTION_INTERVAL_SECONDS = 2.5 # 固定动作间隔,单位秒​
MANUAL_PROMPT_PAUSE_SECONDS = 5 # 手动暂停固定动作时间,单位秒​
SEND_LOCK = threading.Lock()​
AUDIO_TASK_LOCK = threading.Lock()​
FIXED_ACTION_LOCK = threading.Lock()​
ACTIVE_AUDIO_THREAD = None​
ACTIVE_FIXED_ACTION_THREAD = None​
FIXED_ACTION_ENABLED = False​
FIXED_ACTION_PROMPT = ""​
FIXED_ACTION_PAUSED_UNTIL = 0.0​
​
​
# websocket.enableTrace(True)​
​
​
def init(startLiveInfo: dict) -> Optional[websocket.WebSocket]:​
"""​
初始化WebSocket连接​
:return: WebSocket连接对象​
"""​
logging.info("[init] 开始初始化WebSocket连接")​
ws = None​
try:​
ws = websocket.create_connection(​
"wss://openspeech.bytedance.com/virtual_human/avatar_live/live",​
timeout=10, # 连接建立的超时时间(秒)​
)​
# 设置recv方法的超时时间(全局生效)​
ws.settimeout(10)​
​
logging.info(f"[init] 准备发送开播信息: {startLiveInfo}")​
ws.send(StartLiveHeader + json.dumps(startLiveInfo))​
ack = ws.recv()​
logging.info(f"[init] 收到开播响应: {ack}")​
ackInfo = json.loads(ack[8:])​
if ackInfo["code"] != 1000:​
raise Exception(f"[init] [{ackInfo['code']}] {ackInfo['message']}")​
​
t = threading.Thread(target=heartbeat, args=(ws,), daemon=True)​
t.start()​
return ws​
except Exception as e:​
# 初始化失败时主动关闭连接,避免配额泄漏​
logging.error(f"[init] 初始化WebSocket连接失败: {e}")​
if ws:​
try:​
ws.close()​
logging.info("[init] 初始化失败,已关闭WebSocket连接")​
except Exception as close_e:​
logging.error(f"[init] 初始化失败,关闭连接异常: {close_e}")​
raise e​
​
​
def heartbeat(ws):​
"""​
发送心跳线程​
:param ws: WebSocket连接对象​
"""​
while ws.connected:​
try:​
with SEND_LOCK:​
ws.ping()​
logging.info("[heartbeat] 发送心跳包成功")​
time.sleep(5) # 每5秒发一次心跳​
except Exception as e:​
logging.error(f"[heartbeat] Heartbeat failed: {e}")​
break​
​
​
def safeSend(ws: websocket.WebSocket, payload, opcode=websocket.ABNF.OPCODE_TEXT):​
"""​
安全发送消息,确保线程安全​
:param ws: WebSocket连接对象​
:param payload: 要发送的消息内容​
:param opcode: 消息类型,默认文本消息​
"""​
with SEND_LOCK:​
ws.send(payload, opcode=opcode)​
​
​
def startAsyncAudioTask(ws: websocket.WebSocket, send_func, audio_file_path: str, task_name: str):​
"""​
启动异步音频任务,确保线程安全​
:param ws: WebSocket连接对象​
:param send_func: 发送音频文件的函数,参数为 (ws, audio_file_path)​
:param audio_file_path: 音频文件路径(WAV格式)​
:param task_name: 任务名称,用于日志记录和异常处理​
"""​
global ACTIVE_AUDIO_THREAD​
with AUDIO_TASK_LOCK:​
if ACTIVE_AUDIO_THREAD and ACTIVE_AUDIO_THREAD.is_alive():​
logging.info("[startAsyncAudioTask] 当前已有音频任务在发送,请等待完成后再试")​
return​
​
def runner():​
global ACTIVE_AUDIO_THREAD​
try:​
send_func(ws, audio_file_path)​
except Exception as e:​
logging.error(f"[{task_name}] 后台发送失败: {e}")​
finally:​
with AUDIO_TASK_LOCK:​
ACTIVE_AUDIO_THREAD = None​
​
ACTIVE_AUDIO_THREAD = threading.Thread(target=runner, name=task_name, daemon=True)​
ACTIVE_AUDIO_THREAD.start()​
​
​
def setFixedActionPrompt(prompt: str):​
global FIXED_ACTION_PROMPT, FIXED_ACTION_ENABLED​
normalized_prompt = prompt.strip()​
with FIXED_ACTION_LOCK:​
FIXED_ACTION_PROMPT = normalized_prompt​
if not normalized_prompt:​
FIXED_ACTION_ENABLED = False​
if normalized_prompt:​
logging.info(f"[setFixedActionPrompt] 已更新固定动作提示词: {normalized_prompt}")​
else:​
logging.info("[setFixedActionPrompt] 已关闭固定动作")​
​
​
def startFixedActions(ws: websocket.WebSocket):​
global ACTIVE_FIXED_ACTION_THREAD, FIXED_ACTION_ENABLED​
with FIXED_ACTION_LOCK:​
if not FIXED_ACTION_PROMPT:​
print("未设置固定动作提示词,无法启动固定动作")​
return​
if ACTIVE_FIXED_ACTION_THREAD and ACTIVE_FIXED_ACTION_THREAD.is_alive():​
logging.info("[startFixedActions] 固定动作已在运行中")​
return​
FIXED_ACTION_ENABLED = True​
ACTIVE_FIXED_ACTION_THREAD = threading.Thread(target=fixedActions, args=(ws,), daemon=True)​
ACTIVE_FIXED_ACTION_THREAD.start()​
​
​
def stopFixedActions():​
global FIXED_ACTION_ENABLED​
with FIXED_ACTION_LOCK:​
FIXED_ACTION_ENABLED = False​
​
​
def pauseFixedActions(duration_seconds: int = MANUAL_PROMPT_PAUSE_SECONDS):​
global FIXED_ACTION_PAUSED_UNTIL​
with FIXED_ACTION_LOCK:​
FIXED_ACTION_PAUSED_UNTIL = max(FIXED_ACTION_PAUSED_UNTIL, time.time() + duration_seconds)​
​
​
def fixedActions(ws):​
"""​
发送固定动作线程​
:param ws: WebSocket连接对象​
"""​
global ACTIVE_FIXED_ACTION_THREAD, FIXED_ACTION_ENABLED​
try:​
while ws.connected:​
with FIXED_ACTION_LOCK:​
enabled = FIXED_ACTION_ENABLED​
prompt = FIXED_ACTION_PROMPT​
paused_until = FIXED_ACTION_PAUSED_UNTIL​
​
if not enabled:​
break​
if not prompt:​
time.sleep(0.5)​
continue​
if time.time() < paused_until:​
time.sleep(0.2)​
continue​
​
sendPrompt(ws, prompt, pause_fixed_action=False)​
time.sleep(FIXED_ACTION_INTERVAL_SECONDS)​
except Exception as e:​
logging.error(f"[fixedActions] fixedActions failed: {e}")​
finally:​
with FIXED_ACTION_LOCK:​
FIXED_ACTION_ENABLED = False​
ACTIVE_FIXED_ACTION_THREAD = None​
​
​
def sendPrompt(ws: websocket.WebSocket, prompt: str, pause_fixed_action: bool = True):​
"""​
发送提示词指令​
:param ws: WebSocket连接对象​
:param prompt: 提示词内容​
:param pause_fixed_action: 是否暂停固定动作线程​
"""​
prompt = prompt.strip()​
if not prompt:​
raise ValueError("[sendPrompt] prompt 不能为空")​
​
payload = json.dumps({"prompt": prompt}, ensure_ascii=False)​
if pause_fixed_action:​
pauseFixedActions()​
logging.info(f"[sendPrompt] 准备发送指令内容: {payload}")​
safeSend(ws, PromptHeader + payload)​
return ws​
​
​
def sleepUntilNextAudioTick(next_send_at: float):​
sleep_seconds = next_send_at - time.monotonic()​
if sleep_seconds > 0:​
time.sleep(sleep_seconds)​
​
​
def sendStreamingAudio(ws: websocket.WebSocket, audio_file_path: str):​
"""​
发送流式音频文件到数字人直播服务(DAT|02| + Binary PCM)​
:param ws: WebSocket连接对象​
:param audio_file_path: 音频文件路径(WAV格式)​
"""​
try:​
logging.info(f"[sendStreamingAudio] 开始发送音频文件: {audio_file_path}")​
start_at = time.monotonic()​
total_bytes = 0​
​
for pkg_index, pcm_chunk in enumerate(iterPcmChunks(audio_file_path), start=1):​
message = StreamingAudioHeader.encode("utf-8") + pcm_chunk​
safeSend(ws, message, opcode=websocket.ABNF.OPCODE_BINARY)​
​
total_bytes += len(pcm_chunk)​
sleepUntilNextAudioTick(start_at + total_bytes / PCM_BYTES_PER_SECOND)​
​
safeSend(ws, EndStreamingAudioHeader)​
logging.info("[sendStreamingAudio] 音频发送完成,已发送结束指令")​
return ws​
except wave.Error as e:​
raise Exception(f"[sendStreamingAudio] 音频文件解析失败: {e}")​
except Exception as e:​
raise Exception(f"[sendStreamingAudio] 发送音频失败: {e}")​
​
​
def sendStructurationAudio(ws: websocket.WebSocket, audio_file_path: str):​
"""​
发送结构化流式音频文件到数字人直播服务(DAT|04| + JSON)​
:param ws: WebSocket连接对象​
:param audio_file_path: 音频文件路径(WAV格式)​
"""​
try:​
logging.info(f"[sendStructurationAudio] 开始发送音频文件: {audio_file_path}")​
start_at = time.monotonic()​
total_bytes = 0​
​
for pkg_index, pcm_chunk in enumerate(iterPcmChunks(audio_file_path), start=1):​
payload = json.dumps(​
{"audio": base64.b64encode(pcm_chunk).decode("utf-8")},​
ensure_ascii=False,​
)​
safeSend(ws, StreamingstructurationAudioHeader + payload)​
​
total_bytes += len(pcm_chunk)​
sleepUntilNextAudioTick(start_at + total_bytes / PCM_BYTES_PER_SECOND)​
​
safeSend(ws, EndStreamingAudioHeader)​
logging.info("[sendStructurationAudio] 音频发送完成,已发送结束指令")​
return ws​
except wave.Error as e:​
raise Exception(f"[sendStructurationAudio] 音频文件解析失败: {e}")​
except Exception as e:​
raise Exception(f"[sendStructurationAudio] 发送音频失败: {e}")​
​
​
def iterPcmChunks(audio_file_path: str, chunk_bytes: int = MIN_PCM_CHUNK_BYTES) -> Iterator[bytes]:​
"""按协议要求读取 PCM 分片。"""​
if not os.path.exists(audio_file_path):​
raise FileNotFoundError(f"音频文件不存在: {audio_file_path}")​
​
with wave.open(audio_file_path, "rb") as wf:​
if wf.getframerate() != 16000:​
raise ValueError(f"音频采样率必须为16000Hz,当前为{wf.getframerate()}Hz")​
if wf.getnchannels() != 1:​
raise ValueError(f"音频必须为单声道,当前为{wf.getnchannels()}声道")​
if wf.getsampwidth() != 2:​
raise ValueError(f"音频位深必须为16bit,当前为{wf.getsampwidth() * 8}bit")​
​
frame_count = max(1, chunk_bytes // wf.getsampwidth())​
while True:​
pcm_chunk = wf.readframes(frame_count)​
if not pcm_chunk:​
break​
if len(pcm_chunk) < MIN_PCM_CHUNK_BYTES:​
pcm_chunk = pcm_chunk.ljust(MIN_PCM_CHUNK_BYTES, b"\x00")​
yield pcm_chunk​
​
​
def close(ws: websocket.WebSocket):​
"""​
安全关闭WebSocket连接​
:param ws: WebSocket连接对象​
"""​
if not ws or not ws.connected:​
logging.info("[close] WebSocket连接已关闭,无需重复操作")​
return​
try:​
# 发送关播指令​
safeSend(ws, CloseLiveHeader)​
time.sleep(0.5) # 给服务端处理指令的时间​
logging.info("[close] 已发送关播指令")​
except Exception as send_e:​
logging.warning(f"[close] 发送关播指令失败: {send_e}")​
finally:​
try:​
# 关闭WebSocket连接​
ws.close()​
logging.info("[close] WebSocket连接已正常关闭")​
except Exception as close_e:​
logging.error(f"[close] 关闭WebSocket异常: {close_e}")​
​
​
if __name__ == "__main__":​
ws = None​
# TODO 如需测试音频驱动,需要补充音频文件路径​
audio_path = "/.../xxx.wav"​
# TODO 填写你的开播body参数​
startLiveInfo = {​
"live": {"live_id": "xxx_" + str(uuid.uuid4())},​
"auth": {​
"type": 1,​
"appid": "",​
"token": "",​
},​
"avatar": {​
"avatar_type": "flowact-r1",​
"input_mode": "audio",​
"role": "https://xxx.png",​
},​
"streaming": {​
"type": "bytertc",​
"rtc_app_id": "",​
"rtc_room_id": "",​
"rtc_uid": "",​
"rtc_token": "",​
},​
}​
​
try:​
print("正在连接 WebSocket 服务器...")​
ws = init(startLiveInfo)​
print("连接成功!")​
​
while True:​
print("\n请选择操作:")​
print("1. 发送提示词指令")​
print("2. 发送流式音频")​
print("3. 发送结构化流式音频")​
print("4. 设置固定动作提示词 (设置后数字人会一直保持固定动作)")​
print("5. 停止固定动作")​
print("0. 关播退出 (close)")​
​
choice = input("请输入选项 (0/1/2/3/4/5): ").strip()​
​
if choice == "1":​
content = input("请输入指令内容: ").strip()​
if content:​
sendPrompt(ws, content)​
elif choice == "2":​
if audio_path:​
startAsyncAudioTask(ws, sendStreamingAudio, audio_path, "sendStreamingAudio")​
print("流式音频已开始后台发送")​
elif choice == "3":​
if audio_path:​
startAsyncAudioTask(ws, sendStructurationAudio, audio_path, "sendStructurationAudio")​
print("结构化流式音频已开始后台发送")​
elif choice == "4":​
# 可设置固定动作提示词,设置之后,数字人在直播过程中会保持固定动作​
fixed_prompt = input("请输入固定动作提示词(留空则关闭): ").strip()​
setFixedActionPrompt(fixed_prompt)​
if fixed_prompt:​
print(f"固定动作提示词已更新为: {fixed_prompt}")​
startFixedActions(ws)​
print("固定动作已启动")​
else:​
print("固定动作已关闭")​
elif choice == "5":​
stopFixedActions()​
print("固定动作已停止")​
elif choice == "0":​
stopFixedActions()​
close(ws)​
print("连接已关闭")​
break​
else:​
print("无效选项,请重试")​
​
except Exception as e:​
logging.error(f"程序执行异常: {e}")​
logging.error(f"异常堆栈: {traceback.format_exc()}")​
finally:​
# 最终兜底关闭连接,避免配额泄漏​
if ws:​
close(ws)​
​
​
​
​
​
​
最近更新时间:2026.08.06 10:38:26
这个页面对您有帮助吗?
有用
有用
无用
无用