GPT虚拟直播Demo系列(二)|无人直播间实现虚拟人回复粉丝

news2024/11/25 21:34:32

摘要

虚拟人和数字人是人工智能技术在现实生活中的具体应用,它们可以为人们的生活和工作带来便利和创新。在直播间场景里,虚拟人和数字人可用于直播主播、智能客服、营销推广等。接入GPT的虚拟人像是加了超强buff,具备更强大的自然语言处理能力和智能对话能力,可以实现更加智能化、自然化的人机交互。

  • 直播主播:虚拟人可以作为直播间的主播角色,通过与粉丝的对话和互动,提高粉丝的互动效果和兴趣
  • 代替客服:数字人可以作为客服角色,通通过自然语言处理和智能对话,解决客户的问题,并提高客户满意度。
  • 营销推广:虚拟人可以作为品牌形象进行推广,数字人可以通过客观数据进行精准营销,提高粉丝的黏性和忠诚度。

前言

续上一篇文章《GPT虚拟直播Demo系列(一)|GPT接入直播间实现主播与观众互动》 ,我们实现了ChatGPT与ZIM的对接。使得加入聊天群组就相当于加入了直播间,实时与ChatGPT文字互动。但还缺了点什么:直播间可不是只有文字,还有主播!接下来进入本文主题:如何接入虚拟人直播。

虚拟主播我们可以通过即构Avatar进行个人化定制,之前在他们《官网》体验过Avatar Demo,一键可以打造多元化风格,支持Q版、二次元、动漫、拟人等多种风格,即构自研虚拟形象引擎强大AI驱动能力,四种驱动方式:表情驱动、声音驱动、文本驱动、肢体驱动。根据本期Demo需求定制了拟人版本的主播小姐姐。即构AvatarQ版形象软萌可爱含丰富的服饰和妆容素材库,推荐大家去体验。即构Avatar的文本驱动方式刚好符合咱们的业务需求。

在这里插入图片描述

1 加入ZIM房间,实时收发消息

加入ZIM房间跟上一篇文章介绍的nodejs版原理一致:

  1. 先登录ZIM
  2. 加入房间或创建房间
  3. 发送弹幕
  4. 监听房间消息,如果来自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即可加入直播间。一些小伙伴可能并不需要开发一个虚拟人直播平台,而是想着在抖音、快手、视频号等平台实现虚拟人直播。这里提供一个实现思路:

  1. 复用本文代码,可以实现ChatGPT回复、并将回复的文字驱动虚拟人
  2. 使用直播伴侣等工具录制虚拟人,推流到抖音平台
  3. 去github找开源工具,实时爬取直播间的弹幕
  4. 读取到弹幕后,调用ChatGPT得到回复,再回到第1步。

未来想象方面,虚拟人接入GPT可以实现更加智能化、个性化的服务,可以预见的是,未来的虚拟人将更加人性化,通过情感计算等技术,可以实现更加真实、自然的人机交互。虚拟人还可以与物理机器人结合,成为未来的机器人助手,为人们的生活和工作提供更加便利的服务。

5 Github源码

供一个实现思路:

  1. 复用本文代码,可以实现ChatGPT回复、并将回复的文字驱动虚拟人
  2. 使用直播伴侣等工具录制虚拟人,推流到抖音平台
  3. 去github找开源工具,实时爬取直播间的弹幕
  4. 读取到弹幕后,调用ChatGPT得到回复,再回到第1步。

未来想象方面,虚拟人接入GPT可以实现更加智能化、个性化的服务,可以预见的是,未来的虚拟人将更加人性化,通过情感计算等技术,可以实现更加真实、自然的人机交互。虚拟人还可以与物理机器人结合,成为未来的机器人助手,为人们的生活和工作提供更加便利的服务。

5 Github源码

  1. ChatGPT虚拟直播源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/584657.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Postman 接口测试神器

Postman 接口测试神器 Postman 是一个接口测试和 http 请求的神器&#xff0c;非常好用。 官方 github 地址: Postman Inc. GitHub Postman 的优点&#xff1a; 支持各种的请求类型: get、post、put、patch、delete 等支持在线存储数据&#xff0c;通过账号就可以进行迁移…

HY-M5 三维机器视觉系统在工业自动化生产的应用

行业背景&#xff1a; 如今科学技术有了日新月异的变化&#xff0c;工业自动化也在不断地发展。然而&#xff0c;在高强度、高精准的工作环境下&#xff0c;人工操作已经不能适应企业的发展需求&#xff0c;于是机器人的出现便提供了高效快捷的解决方案。为了实现自动化生产并确…

AUTOSAR通信篇 - CAN网络通信(二:CanIf)

在上一篇&#xff0c;我们介绍了CAN模块&#xff0c;接下来我们介绍在CAN模块之上的模块Can Interface&#xff08;CanIf&#xff09;模块。在AUTOSAR软件架构中&#xff0c;CanIf也在BSW层&#xff0c;它处于CAN模块之上紧挨着CAN模块。CanIf是一个硬件独立层&#xff0c;具有…

【MySQL】如何实现单表查询?

在我们对数据进行操作时&#xff0c;查询无疑是至关重要的&#xff0c;查询操作灵活多变&#xff0c;我们可以根据开发的需求&#xff0c;设计高效的查询操作&#xff0c;把数据库中存储的数据展示给用户。 文章目录 前言1. 基础查询1.1 基础查询语法1.2 基础查询练习 2. 条件查…

算法与数据结构-复杂度分析(上)

文章目录 什么是大 O 复杂度表示法为什么要用大 O 复杂度表示法如何分析一段代码的时间复杂度1、只关注循环执行次数最多的一段代码2、加法法则&#xff1a;总复杂度等于量级最大的那段代码的复杂度3、乘法法则&#xff1a;嵌套代码的复杂度等于嵌套内外代码复杂度的乘积 几种常…

Unity老动画系统Animation

1、创建老动画系统 给要制作动画的GameObeject添加Animation组件 2、Animation参数 Animation&#xff1a;默认播放的动画 Animations&#xff1a;该动画组件可以控制的所有动画 Play AutoMatically&#xff1a;是否一开始就自动播放默认动画 Animate Physics&#xff1a;动画…

【JavaSE】Java基础语法(三十二):Stream流

文章目录 1. 体验Stream流2. Stream流的常见生成方式3. Stream流中间操作方法【应用】4. Stream流终结操作方法【应用】5. Stream流的收集操作 1. 体验Stream流 案例需求 按照下面的要求完成集合的创建和遍历 创建一个集合&#xff0c;存储多个字符串元素把集合中所有以"…

Python文件打包成exe文件

文章目录 背景安装pyinstaller开始打包总结 背景 今天因为在线将pdf转为word被收费了&#xff0c;有点不爽&#xff0c;所以自己动手撸一个pdf转word的小工具&#xff0c;想着打包成exe给朋友使用&#xff0c;万一哪天会用到呢&#xff1f; 安装pyinstaller 打开cmd命令窗口…

【AGC】云监控日志服务查询不到Logger日志相关问题

【关键字】 AGC、云监控、日志服务 【问题描述】 开发者反馈在使用AGC云监控&#xff0c;填写了Logger日志&#xff0c;但是在云监控的日志服务查不到的问题。具体如下所述&#xff1a; 云函数按要求写了Logger日志&#xff0c;但是在云监控的日志服务页面查询不到&#xff…

R语言混合效应(多水平/层次/嵌套)模型及 贝叶斯实现技术

回归分析是科学研究中十分重要的数据分析工具。随着现代统计技术发展&#xff0c;回归分析方法得到了极大改进。混合效应模型&#xff08;Mixed effect model&#xff09;&#xff0c;即多水平模型&#xff08;Multilevel model&#xff09;/分层模型(Hierarchical Model)/嵌套…

【计算思维题】少儿编程 蓝桥杯青少组计算思维 数学逻辑思维真题详细解析第7套

少儿编程 蓝桥杯青少组计算思维 数学逻辑思维真题详细解析第7套 1、下图中,乐乐家的位置用数对(4,3)表示,学校在乐乐家西南方向。下列选项中,学校的位置不可能是 A、(5,4) B、(2,2) C、(2,1) D、(1,2) 答案:A 考点分析:主要考查小朋友们的观察能力和方…

springboot聚合项目程打包,提示包不存在

报错提示如下&#xff0c;这是子模块large_screen调用login_security模块时&#xff0c;找不到login_security的包&#xff0c;但是login_security能单独打包成功 项目结构&#xff0c;两个子模块可以启动 解决办法&#xff1a; 父pom,要用 <packaging>pom</packag…

基础sql代码讲解含运行截图(详细版)

用student表为例&#xff0c;表的结构如下&#xff1a; 查询student表中的全部数据 SELECT * FROM student 插入数据&#xff1a; INSERT INTO student (id,name,phone,age) VALUES (2,张,1123,19) 还可以不写字段名字进行插入&#xff0c;但是此种方式必须和数据库字段一一…

3:String类

文章目录 String类1&#xff1a;介绍&#xff1a;2&#xff1a;String类实现了很多的接口&#xff1a;3&#xff1b;String类常用构造器4&#xff1a;不同方式创建String类对象的区别&#xff08;1&#xff09;直接赋值的方式&#xff08;2&#xff09;常规new的方式&#xff0…

租赁行业提供固定资产管理的解决方案

在租赁行业&#xff0c;固定资产管理和盘点是非常重要的环节。然而&#xff0c;由于资产数量庞大、资产分散、资产更新频繁等因素&#xff0c;使得固定资产管理和盘点变得十分复杂和繁琐。为了解决这些问题&#xff0c;易点易动固定资产管理系统应运而生。 易点易动固定资产管…

vulnhub 靶机渗透:Stapler

Stapler nmap扫描21 端口22 53端口80端口目录爆破 139端口666 端口3306端口12380端口获取数据库root权限获取系统立足点提权 其他思路系统立足点1系统立足点2提权1提权2 https://www.vulnhub.com/entry/stapler-1,150/ 靶机ip:192.168.54.27 kali ip:192.168.54.128 nmap扫描 …

【剑指offer】数据结构——树

目录 数据结构——树直接解【剑指offer】07. 重建二叉树【剑指offer】08. 二叉树的下一个结点【剑指offer】26. 树的子结构【剑指offer】27. 二叉树的镜像【剑指offer】28. 对称的二叉树【剑指offer】32.1 从上到下打印二叉树【剑指offer】32.2 从上到下打印二叉树2【剑指offer…

考研C语言第六章

6.2指针 类似寻宝图&#xff0c;先把地址存储到指针变量里面&#xff0c;然后去找这个地址 指针大小 当64bit——8bit 当32bit——4bit 定义指针一定要和里面的数定义一样类型的 6.3指针的传递使用场景 指针的使用场景&#xff1a;传递和偏移 &#xff08;不需要的话就别用指…

opencv_c++学习(二十八)

一、单目相机位姿估计 如上图所示&#xff0c;根据图像的情况反推相机的运动情况。 如实现上述功能则需要拍摄当前物体的图像&#xff0c;然后拍摄一段时间之后物体的图像&#xff0c;然后联合两张图像则可以获取两个时刻的相机位姿关系。 位姿估计函数&#xff1a; bool cv:s…

Musl libc 库成功适配到 openEuler Embedded,推动欧拉嵌入式生态发展

近期&#xff0c;RISC-V SIG 在欧拉嵌入式操作系统上成功实现了 musl libc 的适配&#xff0c;完成了使用 musl libc 库替换 glibc 库构建镜像的工作。目前&#xff0c;以 musl libc 为基础库编译的镜像已在 Raspberry Pi4 开发板上可用&#xff0c;这一成果推动了 openEuler E…