摘要
虚拟人和数字人是人工智能技能在现实日子中的详细运用,它们能够为人们的日子和作业带来便当和创新。在直播间场景里,虚拟人和数字人可用于直播主播、智能客服、营销推行等。接入GPT的虚拟人像是加了超强buff,具有更强大的天然语言处理才能和智能对话才能,能够完结愈加智能化、天然化的人机交互。
- 直播主播:虚拟人能够作为直播间的主播人物,经过与粉丝的对话和互动,进步粉丝的互动效果和爱好
- 替代客服:数字人能够作为客服人物,通经过天然语言处理和智能对话,解决客户的问题,并进步客户满意度。
- 营销推行:虚拟人能够作为品牌形象进行推行,数字人能够经过客观数据进行精准营销,进步粉丝的黏性和忠诚度。
前言
续上一篇文章《「GPT实战」GPT接入直播间完结虚拟人互动》 ,咱们完结了ChatGPT与ZIM的对接。使得参加谈天群组就相当于参加了直播间,实时与ChatGPT文字互动。但还缺了点什么:直播间可不是只有文字,还有主播!接下来进入本文主题:如何接入虚拟人直播。
虚拟主播咱们能够经过即构Avatar进行个人化定制,之前在他们《官网》体会过Avatar Demo,一键能够打造多元化风格,支持Q版、二次元、动漫、拟人等多种风格,即构自研虚拟形象引擎强大AI驱动才能,四种驱动方法:表情驱动、声音驱动、文本驱动、肢体驱动。依据本期Demo需求定制了拟人版别的主播小姐姐。即构AvatarQ版形象软萌心爱含丰富的服饰和妆容素材库,推荐我们去体会。即构Avatar的文本驱动方法刚好符合咱们的业务需求。
1 参加ZIM房间,实时收发音讯
参加ZIM房间跟上一篇文章介绍的nodejs版原理共同:
- 先登录ZIM
- 参加房间或创立房间
- 发送弹幕
- 监听房间音讯,如果来自ChatGPT则朗诵
1.1 创立ZIM目标
首要引进ZIM库后,能够调用ZIM的create函数创立ZIM目标,然后调用ZIM目标的setEventHandler函数,将ZIMEventHandler目标传入。ZIMEventHandler主要用于处理一些回调事情如用户上线等回调事情。
public class ZIMMngr {
/**
* 创立ZIM目标
*/
private ZIM createZIM(Application app, ZIMEventHandler handler) {
// 创立 ZIM 目标,传入 APPID 与 Android 中的 Application
ZIM zim = ZIM.create(KeyCenter.APP_ID, app);
zim.setEventHandler(handler);
return zim;
}
//其他代码略...
}
1.2 群聊-登录、创立房间、参加房间
登录即构服务首选需求token,生成token算法在附件源码已经给出,直接调用即可。可是需求留意,在这个Demo中直接在客户端上生成了,这是十分危险的操作,由于你的密钥和appid暴露出来了,黑客能够经过密钥和appid蹭你的额度费用。因而,主张把token核算放在服务器端生成。
ZIM的createRoom函数用于创立房间,需求供给房间号;joinRoom函数用于参加房间,相同也需求供给房间号。详细代码如下所示:
public class ZIMMngr {
//其他代码略....
/**
* 登录zim
*/
public void login(String userId, CB cb) {
String token = ZIMMngr.getToken(userId);
ZIMMngr.login(zim, token, userId, new ZIMLoggedInCallback() {
@Override
public void onLoggedIn(ZIMError errorInfo) {
if (errorInfo.getCode() != ZIMErrorCode.SUCCESS) {
Log.e(TAG, "login error:" + errorInfo.getMessage());
cb.complete(false, "登录失利");
} else {
cb.complete(true, null);
}
}
});
}
/**
* 参加房间
*/
public void joinRoom(String roomId, CB cb) {
zim.joinRoom(roomId, new ZIMRoomJoinedCallback() {
@Override
public void onRoomJoined(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
Log.e(TAG, ">>" + errorInfo.code);
if (errorInfo.code == ZIMErrorCode.ROOM_DOES_NOT_EXIST) {
cb.complete(false, "房间不存在!");
} else if (errorInfo.code == ZIMErrorCode.SUCCESS || errorInfo.code == ZIMErrorCode.THE_ROOM_ALREADY_EXISTS) {
cb.complete(true, roomInfo.baseInfo.roomName);
}
}
});
}
/**
* 创立房间
*/
public void createRoom(String masterId, String roomId, String roomName, CB cb) {
ZIMRoomInfo groupInfo = new ZIMRoomInfo();
groupInfo.roomID = roomId;
groupInfo.roomName = roomName;
zim.createRoom(groupInfo, new ZIMRoomCreatedCallback() {
@Override
public void onRoomCreated(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
if (errorInfo.code == ZIMErrorCode.SUCCESS) {
inviteJoinRoom(masterId, roomId, CHATGPT_ID, cb);//这儿把chagpt的用户id硬编码
} else {
Log.e(TAG, "创立房间失利:" + errorInfo.message);
cb.complete(false, "房号已存在,请替换一个房间号!");
}
}
});
}
}
1.3 即时通讯完结收发音讯
接下来完结音讯收发,主动发送音讯与监听接纳音讯。留意,这儿由于咱们只重视弹幕音讯,因而非弹幕音讯过滤。发送音讯封装两类:
- P2P
- ROOM
留意,由于咱们这儿只用弹幕音讯,因而ROOM音讯只表明弹幕音讯。
public class ZIMMngr {
//界说特点略....
/**
* 收到房间音讯
*/
private void onRcvMsg(ArrayList<ZIMMessage> messageList) {
if (mListener == null) return;
for (ZIMMessage zimMessage : messageList) {
if (zimMessage instanceof ZIMBarrageMessage) {//只看弹幕音讯
ZIMBarrageMessage zimTextMessage = (ZIMBarrageMessage) zimMessage;
if (zimMessage.getTimestamp() < this.startTime)
continue;
String fromUID = zimTextMessage.getSenderUserID();
ZIMConversationType ztype = zimTextMessage.getConversationType();
String toUID = zimTextMessage.getConversationID();
Msg.MsgType type = Msg.MsgType.P2P;
String data = zimTextMessage.message;
Msg msg = Msg.parseMsg(data, fromUID, toUID, ztype == ZIMConversationType.ROOM);
mListener.onRcvMsg(msg);
}
}
}
/**
* 发送zim音讯
* */
public void sendMsg(Msg msg, CB cb) {
//p2p音讯则发送Text,room发送弹幕类型音讯
ZIMMessage zimMsg = null;
ZIMConversationType type;
if (msg.type == Msg.MsgType.P2P) {
ZIMTextMessage m = new ZIMTextMessage();
m.message = msg.msg;
zimMsg = m;
type = ZIMConversationType.PEER;
} else {
ZIMBarrageMessage m = new ZIMBarrageMessage();
m.message = msg.msg;
zimMsg = m;
type = ZIMConversationType.ROOM;
}
ZIMMessageSendConfig config = new ZIMMessageSendConfig();
// 音讯优先级,取值为 低:1 默许,中:2,高:3
config.priority = ZIMMessagePriority.LOW;
// 设置音讯的离线推送配置
ZIMPushConfig pushConfig = new ZIMPushConfig();
pushConfig.title = "离线推送的标题";
pushConfig.content = "离线推送的内容";
config.pushConfig = pushConfig;
zim.sendMessage(zimMsg, msg.toUID, type, config, new ZIMMessageSentCallback() {
@Override
public void onMessageAttached(ZIMMessage message) {
}
@Override
public void onMessageSent(ZIMMessage message, ZIMError errorInfo) {
cb.complete(errorInfo.code == ZIMErrorCode.SUCCESS, errorInfo.message);
}
});
}
// 其他代码略....
}
上面代码只挑选了要害函数, 更多关于即构ZIM接口与官方Demo能够点击参阅这儿,或许参阅附录源码。
2 创立虚拟形象-即构Avatar
接下来需求创立虚拟形象,读者能够参阅官方文档获取更多详细信息。
需求留意的是,经过官方封装的ZegoCharacterHelper能够十分简略的创立Avatar
。创立虚拟形象封装到setCharacter函数中,在程序初始化期间,需求履行initRes函数,将资源复制到SDCard。作为演示,这儿是将Assets里面的相关资源复制到SDCard。在实际项目中,主张将资源存放在服务器端,经过离线下载的方法存储到SDCard。这样既能够降低安装包的大小,也更灵敏。
public class AvatarMngr implements ZegoAvatarServiceDelegate {
//特点界说略....
/**
* 设置虚拟形象如衣服、头发、性别等
*/
private void setCharacter(User user) {
// 创立 helper 简化调用
// base.bundle 是头模, human.bundle 是全身人模
mCharacterHelper = new ZegoCharacterHelper(FileUtils.getPhonePath(mApp, "human.bundle", "assets"));
mCharacterHelper.setExtendPackagePath(FileUtils.getPhonePath(mApp, "Packages", "assets"));
// 设置形象配置
mCharacterHelper.setDefaultAvatar(ZegoCharacterHelper.MODEL_ID_FEMALE);
// 人物上屏, 必须在 UI 线程, 必须设置过avatar形象后才可调用(用 setDefaultAvatar 或许 setAvatarJson 都能够)
mCharacterHelper.setCharacterView(user.avatarView, () -> {
});
mCharacterHelper.setViewport(ZegoAvatarViewState.half);
mCharacterHelper.setPackage("ZEGO_Girl_Hair_0001");
mCharacterHelper.setPackage("ZEGO_Girl_Tshirt_0001_0002");
mCharacterHelper.setPackage("facepaint5");
mCharacterHelper.setPackage("irises2");
updateUser(user);
}
private void initRes(Application app) {
// 先把资源复制到SD卡,留意:线上运用时,需求做一下判断,防止多次复制。资源也能够做成从网络下载。
if (!FileUtils.checkFile(app, "AIModel.bundle", "assets"))
FileUtils.copyAssetsDir2Phone(app, "AIModel.bundle", "assets");
if (!FileUtils.checkFile(app, "base.bundle", "assets"))
FileUtils.copyAssetsDir2Phone(app, "base.bundle", "assets");
if (!FileUtils.checkFile(app, "human.bundle", "assets"))
FileUtils.copyAssetsDir2Phone(app, "human.bundle", "assets");
if (!FileUtils.checkFile(app, "Packages", "assets"))
FileUtils.copyAssetsDir2Phone(app, "Packages", "assets");
}
//...
//其他代码略....
//...
}
除了衣服、首饰、发型等”装修类”形象界说,还能够捏脸,这儿不详细描绘,主张读者前往官网检查。即构Avatar官网。
4 直播间虚拟人与粉丝互动谈天
创立完虚拟人后,接下来将收到的ChatGPT音讯朗诵出来,使虚拟主播嘴巴动起来,互动玩法更好。首要履行initTextApi函数,初始化本地文字驱动引擎。接下来就能够调用ZegoTextAPI的playTextExpression函数,驱动虚拟人语音播报文字内容。
/**
* 朗诵文字(嘴唇+语音)
*/
public void playText(String text) {
if (mTextApi == null) return;
mTextApi.playTextExpression(text);
Log.e(TAG, ">>>>已播映" + text);
}
/**
* 初始化文本驱动接口
*/
private void initTextApi() {
mTextApi = new ZegoTextAPI(mCharacterHelper.getCharacter());
mTextApi.setTextExpressionCallback(new ITextExpressionCallback() {
/**
* 文本驱动播映启动时,回调
*/
@Override
public void onStart() {
Log.d(TAG, "text drive start");
}
/**
* 文本驱动播映出错时,回调
* @param errorCode 错误码,详情请参阅 [常见错误码 - 文本驱动](https://doc-zh.zego.im/article/14884#2)。
*/
@Override
public void onError(int errorCode, String msg) {
}
/**
* 文本驱动播映结束时,回调
*/
@Override
public void onEnd() {
Log.d(TAG, "text drive end");
}
});
}
文本驱动部分代码比较简略,也反映了官方对这块封装的比较好。播报文字主要借助即构avatar的文本才能,读者能够检查官方文档描绘:官方文档。仔细阅读能够发现,要害核心代码十分少,附件里面的其他代码主要是开发App非核心代码。
本文演示了从0开发、无须服务端开发完结的基于ChatGPT的虚拟人直播,任何人下载该App即可参加直播间。一些小伙伴可能并不需求开发一个虚拟人直播渠道,而是想着在抖音、快手、视频号等渠道完结虚拟人直播。这儿供给一个完结思路:
- 复用本文代码,能够完结ChatGPT回复、并将回复的文字驱动虚拟人
- 运用直播伴侣等工具录制虚拟人,推流到抖音渠道
- 去github找开源工具,实时爬取直播间的弹幕
- 读取到弹暗地,调用ChatGPT得到回复,再回到第1步。
未来幻想方面,虚拟人接入GPT能够完结愈加智能化、个性化的服务,能够预见的是,未来的虚拟人将愈加人性化,经过情感核算等技能,能够完结愈加实在、天然的人机交互。虚拟人还能够与物理机器人结合,成为未来的机器人帮手,为人们的日子和作业供给愈加便当的服务。
5 Github源码
供一个完结思路:
- 复用本文代码,能够完结ChatGPT回复、并将回复的文字驱动虚拟人
- 运用直播伴侣等工具录制虚拟人,推流到抖音渠道
- 去github找开源工具,实时爬取直播间的弹幕
- 读取到弹暗地,调用ChatGPT得到回复,再回到第1步。
未来幻想方面,虚拟人接入GPT能够完结愈加智能化、个性化的服务,能够预见的是,未来的虚拟人将愈加人性化,经过情感核算等技能,能够完结愈加实在、天然的人机交互。虚拟人还能够与物理机器人结合,成为未来的机器人帮手,为人们的日子和作业供给愈加便当的服务。
5 Github源码
- ChatGPT虚拟直播源码