视频直播终端之PC版

前言

前些时候由于接到公司一个视频直播的研发任务,对视频直播领域的知识进行系统学习了一段时间,开发完成了基于RTMP协议的视频推拉流功能,涉及到的终端程序主要有3个版本,分别是微信小程序版、PC版和Web版。应用过程为微信小程序与PC程序可以创建直播房间操作,对方可以用以上的任何终端程序加入房间进行视频对讲。

架构

在这里插入图片描述
Nginx 作为视频流中转服务。
Client1 作为主播终端,发起推流并创建直播房间等待其它人加入。
Client2 作为客播终端,加入正在直播的房间并主动推送自己的流。
这样就可以完成双终端对讲的业务逻辑,Clent1 推送自己的视频流,拉取Client2的视频流,界面可以实现同时观看自己和对方的视频图像。Client2也是一样的逻辑推送自己的视频流,拉取Client1的视频流。这样双方都可以完成视频对接功能。

技术

视频直播其实就是把终端设备的硬件产生的模拟信号数据化的过程。
本实例主要是应用JavaCV中的OpenCVFrameGrabber对电脑端摄像头进行数据采集,通过FrameRecorder将视频流数据以RTMP协义推送到网络媒体服务器。接入终端通过VLC播放器EmbeddedMediaPlayerComponent 对服务器上的视频流进行拉取并渲染播放。

实现

1. 创建SpringBoot工程

不要以为SpringBoot只是开发web的框架,其实它的功能非常强大,不仅能开发B/S程序,还可以辅助开发C/S程序的。本实例就是一个纯Java的C/S程序,创建过程如下。
在这里插入图片描述
在Idea开发环境下创建SpringBoot的详细过程不是我的重点就不细说了,不懂的可以自已百度一下教程。

2. 安装流媒体插件

下载安装VLC播放器程序插件,此插件可以使用VLC播放器内嵌入JFrame窗口中,作为视频流拉取的容器。下载完成后我是把它放到程序根目录下,这不是必须的,看自己意愿吧。
下载地址:
https://download.csdn.net/download/xxxlllbbb/12589061
在这里插入图片描述

3. 增加Maven引用

由于篇幅的原因这里我只贴出关键性的的Maven引用,如果想要完成POM文件可以给我留言。

 <dependency><groupId>org.creation</groupId><artifactId>common.stream</artifactId><version>1.0.0</version></dependency><!-- javavc引用 --><dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.5.1</version></dependency><dependency><groupId>org.bytedeco</groupId><artifactId>javacv-platform</artifactId><version>1.5.1</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.56</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.56</version></dependency><!-- VLC播放器 --><dependency><groupId>uk.co.caprica</groupId><artifactId>vlcj</artifactId><version>3.12.1</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version></dependency><dependency><groupId>org.ini4j</groupId><artifactId>ini4j</artifactId><version>0.5.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version></dependency>
视频流推拉

视频流主要拉取窗口代码如下:

package com.rtmp.jframe;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rtmp.Application;
import com.rtmp.jframe.event.impl.HomeCloseEventImpl;
import com.rtmp.jframe.event.impl.RootWindowCloseEventImpl;
import com.rtmp.utils.ControlUtils;
import com.rtmp.utils.Person;
import com.rtmp.utils.Result;
import com.rtmp.utils.Room;
import com.rtmp.view.JImagePanel;
import com.sun.jna.NativeLibrary;
import okhttp3.*;
import org.creation.common.stream.rtmp.*;
import org.creation.common.string.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import uk.co.caprica.vlcj.binding.LibVlc;
import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.discovery.NativeDiscovery;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;
import uk.co.caprica.vlcj.runtime.RuntimeUtil;import javax.sound.midi.Soundbank;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.List;@Component
public class HomeView extends JFrame {@Value("${rtmp.get}")private String rtmpGet;@Value("${rtmp.remove}")private String rtmpRemove;@Value("${rtmp.delete}")private String rtmpDelete;@Value("${rtmp.push}")private String rtmpPushUrl;@Value("${rtmp.pull}")private String rtmpPullUrl;private JImagePanel pushPlayer;private EmbeddedMediaPlayerComponent pullPlayer;private String playerPath = "D:\\\\workspaceIdea\\com.rtmp.app\\player";private Pusher pusher;JButton btn1;JButton btnBg;private int width = 815;private int height = 600;private JLayeredPane layeredPanel;@AutowiredSystemView systemView;@AutowiredListView listView;@AutowiredHomeView homeView;private int k = 0;private JLabel jz;private String pushId;private String pullId;private String roomId;private String roomType;//重写这个方法@Overrideprotected void processWindowEvent(WindowEvent e) {if (e.getID() == WindowEvent.WINDOW_CLOSING) {listView.setVisible(true);homeView.setVisible(false);try {if (pusher != null) {pusher.close();}if (pullPlayer != null) {getMediaPlayer().stop();}if (roomType.equals("add")) {Request.Builder reqBuild = new Request.Builder();HttpUrl.Builder urlBuilder = HttpUrl.parse(systemView.serviceUrl + rtmpRemove).newBuilder();urlBuilder.addQueryParameter("roomid", roomId);urlBuilder.addQueryParameter("id", pushId);reqBuild.url(urlBuilder.build());Request request = reqBuild.build();OkHttpClient okHttpClient = new OkHttpClient();Response response1 = okHttpClient.newCall(request).execute();} else if (roomType.equals("create")) {Request.Builder reqBuild = new Request.Builder();HttpUrl.Builder urlBuilder = HttpUrl.parse(systemView.serviceUrl + rtmpDelete).newBuilder();urlBuilder.addQueryParameter("id", roomId);reqBuild.url(urlBuilder.build());Request request = reqBuild.build();OkHttpClient okHttpClient = new OkHttpClient();Response response1 = okHttpClient.newCall(request).execute();}} catch (Exception c) {}return; //直接返回,阻止默认动作,阻止窗口关闭}super.processWindowEvent(e); //该语句会执行窗口事件的默认动作(如:隐藏)}public HomeView() {super("Cre视频直播组件");setResizable(false);
//        setUndecorated(true);//去除标题栏
//        getRootPane().setWindowDecorationStyle(JRootPane.PLAIN_DIALOG); //采用指定的窗口装饰风格setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);enableEvents(AWTEvent.WINDOW_EVENT_MASK);getContentPane().setLayout(null);RootWindowCloseEventImpl rootCloseEvt = new RootWindowCloseEventImpl();addWindowListener(rootCloseEvt);Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();setBounds((screenSize.width - width) / 2, (screenSize.height - height) / 2, width, height);initializing();}private void initializing() {layeredPanel = new JLayeredPane();layeredPanel.setLayout(null);layeredPanel.setBounds(0, 0, width, height);layeredPanel.setOpaque(false);getContentPane().add(layeredPanel);/*** 增加拉流器面板*/initializingMediaPlayer();btnBg = new JButton();btnBg.setBounds(0, 0, width, height);btnBg.setBorderPainted(false);btnBg.setBackground(Color.black);btnBg.setIcon(new ImageIcon(ControlUtils.class.getResource(String.format("/image/%s", "bg.jpg"))));layeredPanel.add(btnBg, new Integer(101));/*** 增加推流器面板*/pushPlayer = ControlUtils.addPanelOfContentPane(0, 0, 200, 200, null, layeredPanel, JLayeredPane.MODAL_LAYER);/*** 关闭推流板*/btn1 = new JButton();btn1.setBounds(180, 2, 20, 20);btn1.setBorderPainted(false);btn1.setContentAreaFilled(false);btn1.setIcon(new ImageIcon(ControlUtils.class.getResource(String.format("/image/%s", "5.png"))));layeredPanel.add(btn1, new Integer(400));btn1.addActionListener(e -> {layeredPanel.remove(pushPlayer);btn1.setVisible(false);});/*** @增加开始按钮*/ControlUtils.addButtonOfContentPane("1.png", layeredPanel, e -> {if (!StringUtil.isEmpty(pushId)) {
//                invoke(pushId);invokePusher();invokePuller();btnBg.setVisible(false);} else {
//                btn1.setVisible(false);String pushUrl = systemView.push_text.getText();String pullUrl = systemView.pull_text.getText();rtmpPushUrl = pushUrl + "/" + pushId;rtmpPullUrl = pullUrl + "/" + pullId;invokePusher();invokePuller();btnBg.setVisible(false);}}, null);/*** @增加结束按钮*/ControlUtils.addButtonOfContentPane("0.png", layeredPanel, e -> {try {if (pusher != null) {pusher.close();}
//                if (pullPlayer != null) {
//                    getMediaPlayer().stop();
//                }} catch (Exception ex) {ex.printStackTrace();}}, btn -> {btn.setBounds(250, 450, 64, 64);});}public void invoke2(String pushId, String roomId, String pullId) {if (pusher != null) {pusher = null;}this.roomId = roomId;roomType = "add";btn1.setVisible(false);rtmpPushUrl = systemView.pushUrl;rtmpPullUrl = systemView.pullUrl;this.pushId = pushId;this.pullId = pullId;jz = new JLabel("加载中", JLabel.CENTER);Font font = new Font("宋体", Font.BOLD, 25);//创建1个字体实例jz.setForeground(Color.RED);jz.setFont(font);jz.setBounds(0, 0, width, height);layeredPanel.add(jz, new Integer(900));// 线程的另一种实现方法,也可以使用匿名的内部类Thread2 thread2 = new Thread2();thread2.start();}class Thread2 extends Thread {@Overridepublic void run() {rtmpPushUrl = rtmpPushUrl + "/" + pushId;invokePusher();btn1.setVisible(true);jz.setVisible(false);rtmpPullUrl = rtmpPullUrl + "/" + pullId;invokePuller();btnBg.setVisible(false);}}class Thread1 extends Thread {@Overridepublic void run() {rtmpPushUrl = rtmpPushUrl + "/" + pushId;invokePusher();btn1.setVisible(true);if (!StringUtil.isEmpty(pushId)) {asyncSend();}}}public void invoke(String id) {if (pusher != null) {pusher = null;}btnBg.setVisible(true);k = 0;roomId = "room_rtmp_" + id;roomType = "create";btn1.setVisible(false);rtmpPushUrl = systemView.pushUrl;rtmpPullUrl = systemView.pullUrl;pushId = id;jz = new JLabel("加载中", JLabel.CENTER);Font font = new Font("宋体", Font.BOLD, 25);//创建1个字体实例jz.setForeground(Color.RED);jz.setFont(font);jz.setBounds(0, 0, width, height);layeredPanel.add(jz, new Integer(900));// 线程的另一种实现方法,也可以使用匿名的内部类Thread1 thread1 = new Thread1();thread1.start();}private void initializingMediaPlayer() {String path = getAppPath(HomeView.class);if (path.indexOf("BOOT-INF") > -1) {playerPath = path;playerPath += "/classes/player";playerPath = playerPath.substring(0, playerPath.substring(0, playerPath.indexOf(".jar")).lastIndexOf("/")) + "/player";}NativeLibrary.addSearchPath(RuntimeUtil.getLibVlcLibraryName(), playerPath);LibVlc.INSTANCE.libvlc_get_version();new NativeDiscovery().discover();pullPlayer = new EmbeddedMediaPlayerComponent();pullPlayer.setBounds(0, 0, width, height);layeredPanel.add(pullPlayer, JLayeredPane.DEFAULT_LAYER);}private EmbeddedMediaPlayer getMediaPlayer() {return pullPlayer.getMediaPlayer();}/*** 开始拉流*/private void invokePuller() {getMediaPlayer().playMedia(rtmpPullUrl);}/*** 开始推流*/private void invokePusher() {//System.out.println(rtmpPushUrl);try {if (pusher == null) {pusher = new Pusher(rtmpPushUrl);pusher.invoke(bg -> {pushPlayer.setBackground(bg);pushPlayer.validate();pushPlayer.repaint();});}pusher.start();} catch (java.lang.Exception ex) {ex.printStackTrace();}}private void asyncSend() {Request.Builder reqBuild = new Request.Builder();HttpUrl.Builder urlBuilder = HttpUrl.parse(systemView.serviceUrl + rtmpGet).newBuilder();urlBuilder.addQueryParameter("id", roomId);reqBuild.url(urlBuilder.build());Request request = reqBuild.build();OkHttpClient okHttpClient = new OkHttpClient();Call call = okHttpClient.newCall(request);//异步处理call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {
//                Log.e("onFailure", "onFailure");}@Overridepublic void onResponse(Call call, Response response) throws IOException {Result result = JSON.parseObject(response.body().string(), Result.class);Room room = JSON.parseObject(result.getResult().toString(), Room.class);if (k == 0) {List<Person> personList = room.getPersons();if (personList.size() > 1) {k = 1;layeredPanel.remove(jz);for (int j = 0; j < personList.size(); j++) {Person person = personList.get(j);if (!person.getId().equals(pushId)) {pullId = person.getId();}}rtmpPullUrl += "/" + pullId;invokePuller();btnBg.setVisible(false);} else {asyncSend();}}}});}public static String getAppPath(Class cls) {//检查用户传入的参数是否为空if (cls == null)throw new java.lang.IllegalArgumentException("参数不能为空!");ClassLoader loader = cls.getClassLoader();//获得类的全名,包括包名String clsName = cls.getName() + ".class";//获得传入参数所在的包Package pack = cls.getPackage();String path = "";//如果不是匿名包,将包名转化为路径if (pack != null) {String packName = pack.getName();//此处简单判定是否是Java基础类库,防止用户传入JDK内置的类库if (packName.startsWith("java.") || packName.startsWith("javax."))throw new java.lang.IllegalArgumentException("不要传送系统类!");//在类的名称中,去掉包名的部分,获得类的文件名clsName = clsName.substring(packName.length() + 1);//判定包名是否是简单包名,如果是,则直接将包名转换为路径,if (packName.indexOf(".") < 0) path = packName + "/";else {//否则按照包名的组成部分,将包名转换为路径int start = 0, end = 0;end = packName.indexOf(".");while (end != -1) {path = path + packName.substring(start, end) + "/";start = end + 1;end = packName.indexOf(".", start);}path = path + packName.substring(start) + "/";}}//调用ClassLoader的getResource方法,传入包含路径信息的类文件名java.net.URL url = loader.getResource(path + clsName);//从URL对象中获取路径信息String realPath = url.getPath();//去掉路径信息中的协议名"file:"int pos = realPath.indexOf("file:");if (pos > -1) realPath = realPath.substring(pos + 5);//去掉路径信息最后包含类文件信息的部分,得到类所在的路径pos = realPath.indexOf(path + clsName);realPath = realPath.substring(0, pos - 1);//如果类文件被打包到JAR等文件中时,去掉对应的JAR等打包文件名if (realPath.endsWith("!"))realPath = realPath.substring(0, realPath.lastIndexOf("/"));/*------------------------------------------------------------ClassLoader的getResource方法使用了utf-8对路径信息进行了编码,当路径中存在中文和空格时,他会对这些字符进行转换,这样,得到的往往不是我们想要的真实路径,在此,调用了URLDecoder的decode方法进行解码,以便得到原始的中文及空格路径-------------------------------------------------------------*/try {realPath = java.net.URLDecoder.decode(realPath, "utf-8");} catch (Exception e) {throw new RuntimeException(e);}return realPath;}//getAppPath定义结束
}

playerPath是VLC播放器程序插件安装路径。
EmbeddedMediaPlayerComponent就是VLC播放器管理组件,通过它可以调用播放器对RTMP流进行拉取。
rtmpPushUrl设置程序运行以后的视频流推送地址。
rtmpPullUrl设置程序运行以后的视频流拉取地址。

运行

在这里插入图片描述
实例程序下载
https://download.csdn.net/download/xxxlllbbb/12589559

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑端看电视直播

文章目录 1.PotPlayer介绍2.软件安装3.使用PotPlayer看电视直播 1.PotPlayer介绍 PotPlayer是一款强大的视频播放软件&#xff0c;可用于各种音频、视频格式无需外部解码器或程序即可播放。也可用于单播和多播的视频服务。可在[0.2~12]之间倍速播放。中文软件完美解码已经整合…

《TT语音》用户体验分析报告

一、背景游览 1. 产品简介 【TT语音】是广州趣丸网络科技有限公司旗下的一款社交应用&#xff0c;自2014年上线以来&#xff0c;TT语音已累计超一亿注册用户。实力不容小觑。 【TT语音】的口号是“让天下没有孤单的玩家”&#xff0c;主打游戏社交。通过连麦开黑&#xff0c…

训练Chatgpt进行小红书批量创作SOP+操作演示

今天给大家分享&#xff0c;如何使用有效的Prompt对ChatGPT进行训练&#xff0c;帮助我们在创作小红书笔记时更加高效。【文章太干&#xff0c;看到最后一定有收获&#xff01;】 ChatGPT的强大在于其核心技术——"Prompt"&#xff0c;它是用户提供的问题或文本&…

剧本杀,继狼人杀之后的下一个风口

“剧本杀”简介 2018年上半年&#xff0c;随着几款连麦推理社交游戏的上架&#xff0c;“剧本杀”一词开始迅速走红&#xff0c;有望成为继狼人杀之后的下一个风口级游戏。 “剧本杀”最初源自线下游戏“谋杀之谜”&#xff0c;是一款 LARP &#xff08;实时角色扮演&#xf…

chatgpt赋能python:Python如何读取npz文件?

Python 如何读取 npz 文件&#xff1f; 在 Python 中&#xff0c;我们可以使用多种方式读取 npz 格式的文件。npz 文件是 numpy 的压缩文件格式&#xff0c;可以用于存储多个数组数据。 什么是 npz 文件&#xff1f; npz 文件是一个多个数组数据的压缩文件格式&#xff0c;它…

帅到没朋友

没有开通朋友圈&#xff0c;或者朋友圈只有自己的&#xff0c;都算“帅到没朋友”。 (本笔记适合熟练应用python列表list的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学…

2023最新Onekey卡注册使用指南| Visa虚拟卡| U币充值|国内 支付场景实测

写这篇文章的目的是推荐Onekey的Visa虚拟卡的&#xff0c;当然也是为了参与Onekey卡的活动。我觉得虚拟卡的一个基本要求就是&#xff1a;好使及使用场景广泛&#xff0c;其次就是损耗较为合理。所以我找到了这一款Onekey虚拟卡。 一、介绍 Onekey虚拟卡和Onekey硬件钱包是同一…

小白入坑 Web 渗透测试必备指南

本文来自作者 肖志华 在 GitChat 上分享「Web 渗透测试入坑必备指南」&#xff0c;「阅读原文」查看交流实录 「文末高能」 编辑 | 黑石 小白如何快速入门 由于本人技术性受限制&#xff0c;可能部分内容显得不那么清晰&#xff0c;如有疑问请读者圈联系我。再者&#xff0c;内…

微信小程序——mock.js模拟后台交互

1下载mock.js (function webpackUniversalModuleDefinition(root, factory) {if(typeof exports object && typeof module object)module.exports factory();else if(typeof define function && define.amd)define([], factory);else if(typeof exports o…

肝了一个月的原创小袁个人博客项目开源啦(博客基本功能都有,还包含后台管理)

提前说明 项目V2.0微服务版本已经发布&#xff0c;详细跳转下面链接 https://blog.csdn.net/weixin_47971206/article/details/128119409?spm1001.2014.3001.5502 小袁有话说 从前端页面的设计到后端接口的开发到整个项目的开发&#xff0c;前前后后断断续续的花了差不多二十…

CSDN前1000名博主

博主简介stpeace排名&#xff1a;1 原创&#xff1a;2166 粉丝&#xff1a;7180 积分&#xff1a;181660 等级&#xff1a;10stpeace的专栏中国本博客供大家交流&#xff0c;欢迎各抒己见。博文中的内容禁止用yuanmeng001排名&#xff1a;2 原创&#xff1a;5286 粉丝&#xff…

如何避开微信小程序的审核机制(实测有效)

群聊&#xff08;项目源码&#xff09; 如何避开微信小程序的审核机制(实测有效) 1.话不多说&#xff0c;看效果 2.微信小程序的审核机制&#xff0c;是相当严&#xff0c;对于新手来说是一个很不友好的机制。但是办法总比困难多。 下面我就教大家如何实现项目的上传和如何避…

推荐CSDN排名前1000博主

推荐CSDN排名前1000博主 https://blog.csdn.net/ZYC88888/article/details/98479146?utm_mediumdistribute.pc_relevant_bbs_down.none-task--2~all~first_rank_v2~rank_v29-23.nonecase&depth_1-utm_sourcedistribute.pc_relevant_bbs_down.none-task--2~all~first_rank…

支付宝小程序对接流量位详细教程

最近接触了好几个支付宝小程序&#xff0c;支付宝小程序没有流量主概念&#xff0c;应该说是没有CPM类型的广告&#xff0c;这个流量位算是淘宝客的扩展版&#xff0c;使用的是CPA模式。但是对于某些用户来说应该还是有点用处的&#xff0c;今天给大家一个详细的流量位接入教程…

chatgpt赋能python:Python购买要钱吗?

Python购买要钱吗&#xff1f; 如果你正在考虑学习编程&#xff0c;那么Python是一个很好的选择。Python是一种易学易用的高级编程语言。然而&#xff0c;有些人可能会问&#xff0c;Python购买要钱吗&#xff1f;在这篇文章中&#xff0c;我们将回答这个问题并探讨一下Python…

如何使用Edge Copilot

在ChatGPT红得发紫的年代&#xff0c;也应该了解下微软microsoft edge浏览器内嵌的Copilot。 通过这个Copilot - 副驾驶&#xff0c;Edge浏览器发生了革命性变化。 借助AI副驾驶强大的功能&#xff0c;在Edge浏览器中可以轻松完成AI聊天、AI写作、AI网页分析&#xff0c;和AI…

借助亚马逊云科技生成你的第一张AIGC图片

一幅绘画引领的AIGC(AI Generated Content&#xff0c;人工智能生成内容)热潮&#xff01; 2022年9月AI绘画“太空歌剧院”获得美国科罗拉多州博览会一等奖&#xff0c;同年11月底ChatGPT&#xff08;人工智能对话聊天机器人&#xff09;推出并迅速风靡全球&#xff0c;从时间节…

新手如何使用pycharm

开启coding之旅 我在写代码的时候&#xff0c;我总是因为安装包和环境的问题&#xff0c;代码总是bug不断&#xff0c;短段的两天时间&#xff0c;我整天扑在上面研究&#xff0c;不断地抓头&#xff0c;短段两天&#xff0c;我感觉我的头发都凸了一点了 后来我实在解决不了&a…

chatgpt赋能python:Python中去除重复项的方法

Python中去除重复项的方法 Python作为一种高级编程语言&#xff0c;已经被广泛应用于各个领域。在日常编程中&#xff0c;我们常常需要对数据进行处理&#xff0c;而其中一个常见的问题就是如何去除重复项。本文将介绍如何使用Python语言去除两个列表中的重复项。 什么是列表…

AI智能体的核心驱动力是大语言模型——规划、记忆和工具使用是三大关键组件...

“ 立志实现 AGI 的 OpenAI&#xff0c;是不是已经在暗中做起了大模型智能体&#xff1f; ” 来源&#xff1a;AIII研究院 最近几个月&#xff0c;随着大语言模型的持续火爆&#xff0c;利用其构建 AI 智能体的研究陆续进入人们的视线。AI 智能体这个概念也流行开来&#xff0c…