音视频入门基础:RTP专题(9)——FFmpeg接收RTP流的原理和内部实现
一、引言
由《音视频入门基础:RTP专题(2)——使用FFmpeg命令生成RTP流》可以知道,推流端通过下面FFmpeg命令可以将一个媒体文件转推RTP,生成RTP流:
ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec copy -an -f rtp rtp://192.168.0.103:6005 -acodec copy -vn -sdp_file XXX.sdp -f rtp rtp://192.168.0.103:7005
接收端通过命令:ffmpeg -protocol_whitelist "file,rtp,udp" -i XXX.sdp 可以查看生成的RTP流的信息:

由《音视频入门基础:RTP专题(8)——使用Wireshark分析RTP》可以知道,上述推流端的本质是创建了一个UDP客户端,将媒体文件input.mp4的视频数据发送到IP为192.168.0.103的UDP服务器的6005端口,音频数据发送到该UDP服务器的7005端口。与之对应,接收端的本质是创建了一个UDP服务器来接收推流端发送的基于UDP的RTP数据。下面讲述接收端的FFmpeg,其源码中接收RTP流的内部实现。
二、FFmpeg接收RTP流的内部实现
(一)获取端口
由《音视频入门基础:RTP专题(3)——SDP简介》可以知道,SDP中某行<type>的值为'm'时,<value>会包含媒体描述信息,此时该行SDP的格式为:m=<media> <port> <proto> <fmt> ...。如果音视频数据为RTP,那<port>为接收端需要创建的UDP服务器的端口号,该端口用于接收推流端发送的基于UDP的RTP数据。
由《音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现》可以知道,FFmpeg源码中通过ff_sdp_parse函数解析SDP,而ff_sdp_parse函数中又会通过sdp_parse_line函数解析SDP中的一行数据,所以sdp_read_header函数中执行完ff_sdp_parse函数后,变量rtsp_st->sdp_port会得到<port>的信息。然后sdp_read_header函数中会通过语句:ff_url_join(url, sizeof(url), "rtp", NULL,namebuf, rtsp_st->sdp_port,"?localport=%d&ttl=%d&connect=%d&write_to_source=%d",rtsp_st->sdp_port, rtsp_st->sdp_ttl,rt->rtsp_flags & RTSP_FLAG_FILTER_SRC ? 1 : 0,rt->rtsp_flags & RTSP_FLAG_RTCP_TO_SOURCE ? 1 : 0) 将包含<port>信息的字符串拷贝到url数组中:
static int sdp_read_header(AVFormatContext *s)
{RTSPState *rt = s->priv_data;char url[MAX_URL_SIZE]
//...err = ff_sdp_parse(s, bp.str);av_bprint_finalize(&bp, NULL);if (err) goto fail;/* open each RTP stream */for (i = 0; i < rt->nb_rtsp_streams; i++) {char namebuf[50];rtsp_st = rt->rtsp_streams[i];if (!(rt->rtsp_flags & RTSP_FLAG_CUSTOM_IO)) {AVDictionary *opts = map_to_opts(rt);char buf[MAX_URL_SIZE];const char *p;err = getnameinfo((struct sockaddr*) &rtsp_st->sdp_ip,sizeof(rtsp_st->sdp_ip),namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST);if (err) {av_log(s, AV_LOG_ERROR, "getnameinfo: %s\n", gai_strerror(err));err = AVERROR(EIO);av_dict_free(&opts);goto fail;}ff_url_join(url, sizeof(url), "rtp", NULL,namebuf, rtsp_st->sdp_port,"?localport=%d&ttl=%d&connect=%d&write_to_source=%d",rtsp_st->sdp_port, rtsp_st->sdp_ttl,rt->rtsp_flags & RTSP_FLAG_FILTER_SRC ? 1 : 0,rt->rtsp_flags & RTSP_FLAG_RTCP_TO_SOURCE ? 1 : 0);p = strchr(s->url, '?');if (p && av_find_info_tag(buf, sizeof(buf), "localaddr", p))av_strlcatf(url, sizeof(url), "&localaddr=%s", buf);else if (rt->localaddr && rt->localaddr[0])av_strlcatf(url, sizeof(url), "&localaddr=%s", rt->localaddr);append_source_addrs(url, sizeof(url), "sources",rtsp_st->nb_include_source_addrs,rtsp_st->include_source_addrs);append_source_addrs(url, sizeof(url), "block",rtsp_st->nb_exclude_source_addrs,rtsp_st->exclude_source_addrs);err = ffurl_open_whitelist(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ,&s->interrupt_callback, &opts, s->protocol_whitelist, s->protocol_blacklist, NULL);av_dict_free(&opts);if (err < 0) {err = AVERROR_INVALIDDATA;goto fail;}}if ((err = ff_rtsp_open_transport_ctx(s, rtsp_st)))goto fail;}
//..
}
然后sdp_read_header中会执行语句:err = ffurl_open_whitelist(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ,&s->interrupt_callback, &opts, s->protocol_whitelist, s->protocol_blacklist, NULL)。ffurl_open_whitelist函数中会通过语句:int ret = ffurl_alloc(puc, filename, flags, int_cb)将上述url数组中的字符串拷贝到(*puc)->filename中。然后ffurl_open_whitelist函数中会调用ffurl_connect函数:
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options,const char *whitelist, const char* blacklist,URLContext *parent)
{
//...int ret = ffurl_alloc(puc, filename, flags, int_cb);
//...ret = ffurl_connect(*puc, options);//...
}
ffurl_connect函数中会执行函数指针url_open指向的回调函数。当音视频流为RTP流时,对应的回调函数就是rtp_open函数:
int ffurl_connect(URLContext *uc, AVDictionary **options)
{
//...err =uc->prot->url_open2 ? uc->prot->url_open2(uc,uc->filename,uc->flags,options) :uc->prot->url_open(uc, uc->filename, uc->flags);
//...
}
rtp_open函数的底层又会调用udp_open函数,udp_open函数中会通过下面语句将上述字符串中的<port>信息提取出来,赋值给变量s->local_port:
static int udp_open(URLContext *h, const char *uri, int flags)
{
//...p = strchr(uri, '?');if (p) {//...if (av_find_info_tag(buf, sizeof(buf), "localport", p)) {s->local_port = strtol(buf, NULL, 10);//...}}//...
}
总之,经过一系列繁琐的赋值和拷贝操作后,变量s->local_port最终得到接收端需要创建的UDP服务器的端口号,该端口来源于推流端的FFmpeg命令生成的SDP文件中的端口信息。
(二)给addrinfo结构赋值
获取完端口后,udp_open函数内部会调用udp_socket_create函数:
/* put it in UDP context */
/* return non zero if error */
static int udp_open(URLContext *h, const char *uri, int flags)
{
//...udp_fd = udp_socket_create(h, &my_addr, &len, s->localaddr);
//...
}
udp_socket_create函数内部会通过语句:
res0 = ff_ip_resolve_host(h, (localaddr && localaddr[0]) ? localaddr : NULL,s->local_port,SOCK_DGRAM, family, AI_PASSIVE) 根据给定的主机名和需要创建的UDP服务器的端口,返回一个struct addrinfo结构,从而给addrinfo结构赋值。其中localaddr包含主机名,s->local_port为上述讲到的需要创建的UDP服务器的端口:
static int udp_socket_create(URLContext *h, struct sockaddr_storage *addr,socklen_t *addr_len, const char *localaddr)
{UDPContext *s = h->priv_data;int udp_fd = -1;struct addrinfo *res0, *res;int family = AF_UNSPEC;if (((struct sockaddr *) &s->dest_addr)->sa_family)family = ((struct sockaddr *) &s->dest_addr)->sa_family;res0 = ff_ip_resolve_host(h, (localaddr && localaddr[0]) ? localaddr : NULL,s->local_port,SOCK_DGRAM, family, AI_PASSIVE);if (!res0)goto fail;for (res = res0; res; res=res->ai_next) {if (s->udplite_coverage)udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, IPPROTO_UDPLITE, h);elseudp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0, h);if (udp_fd != -1) break;ff_log_net_error(h, AV_LOG_ERROR, "socket");}if (udp_fd < 0)goto fail;memcpy(addr, res->ai_addr, res->ai_addrlen);*addr_len = res->ai_addrlen;freeaddrinfo(res0);return udp_fd;fail:if (udp_fd >= 0)closesocket(udp_fd);if(res0)freeaddrinfo(res0);return -1;
}
ff_ip_resolve_host函数定义在libavformat/ip.c中,可以看到其内部通过getaddrinfo函数根据给定的主机名和服务名,返回一个struct addrinfo结构。关于getaddrinfo函数的用法可以参考:《百度百科——getaddrinfo》:
struct addrinfo *ff_ip_resolve_host(void *log_ctx,const char *hostname, int port,int type, int family, int flags)
{struct addrinfo hints = { 0 }, *res = 0;int error;char sport[16];const char *node = 0, *service = "0";if (port > 0) {snprintf(sport, sizeof(sport), "%d", port);service = sport;}if ((hostname) && (hostname[0] != '\0') && (hostname[0] != '?')) {node = hostname;}hints.ai_socktype = type;hints.ai_family = family;hints.ai_flags = flags;if ((error = getaddrinfo(node, service, &hints, &res))) {res = NULL;av_log(log_ctx, AV_LOG_ERROR, "getaddrinfo(%s, %s): %s\n",node ? node : "unknown",service,gai_strerror(error));}return res;
}
执行完上述操作后,udp_open函数中的变量my_addr会得到主机名和需要创建的UDP服务器的端口信息:
/* put it in UDP context */
/* return non zero if error */
static int udp_open(URLContext *h, const char *uri, int flags)
{
//...udp_fd = udp_socket_create(h, &my_addr, &len, s->localaddr);
//...
}
(三)创建套接字
给addrinfo结构赋值后,udp_socket_create函数内部会执行语句:udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0, h) 来创建套接字,SOCK_DGRAM表示是基于UDP:
static int udp_socket_create(URLContext *h, struct sockaddr_storage *addr,socklen_t *addr_len, const char *localaddr)
{
//...res0 = ff_ip_resolve_host(h, (localaddr && localaddr[0]) ? localaddr : NULL,s->local_port,SOCK_DGRAM, family, AI_PASSIVE);
//...for (res = res0; res; res=res->ai_next) {if (s->udplite_coverage)udp_fd = ff_socket(res->ai_family, SOCK_DGRAM, IPPROTO_UDPLITE, h);elseudp_fd = ff_socket(res->ai_family, SOCK_DGRAM, 0, h);if (udp_fd != -1) break;ff_log_net_error(h, AV_LOG_ERROR, "socket");}
//...
}
ff_socket函数定义在libavformat/network.c中,可以看到该函数内部通过socket函数创建了套接字。音视频流为RTP的情况下,形参type的值为SOCK_DGRAM,所以此时创建的是UDP套接字:
int ff_socket(int af, int type, int proto, void *logctx)
{int fd;#ifdef SOCK_CLOEXECfd = socket(af, type | SOCK_CLOEXEC, proto);if (fd == -1 && errno == EINVAL)
#endif{fd = socket(af, type, proto);
#if HAVE_FCNTLif (fd != -1) {if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)av_log(logctx, AV_LOG_DEBUG, "Failed to set close on exec\n");}
#endif}
#ifdef SO_NOSIGPIPEif (fd != -1) {if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){1}, sizeof(int))) {av_log(logctx, AV_LOG_WARNING, "setsockopt(SO_NOSIGPIPE) failed\n");}}
#endifreturn fd;
}
(四)绑定套接字
创建完UDP套接字后,udp_open函数中会通过bind函数绑定上述创建的UDP套接字到my_addr上。从上面我们已经可以知道,my_addr包含需要创建的UDP服务器的端口信息:
/* put it in UDP context */
/* return non zero if error */
static int udp_open(URLContext *h, const char *uri, int flags)
{
//...udp_fd = udp_socket_create(h, &my_addr, &len, s->localaddr);
//.../* bind to the local address if not multicast or if the multicast* bind failed *//* the bind is needed to give a port to the socket now */if (bind_ret < 0 && bind(udp_fd,(struct sockaddr *)&my_addr, len) < 0) {ff_log_net_error(h, AV_LOG_ERROR, "bind failed");ret = ff_neterrno();goto fail;}
//...
}
(五)监视文件描述符是否可读并接收数据
绑定完套接字后,FFmpeg的avformat_find_stream_info函数底层会调用rtp_read函数,而rtp_read函数内部会通过poll函数监视文件描述符(上述创建的UDP套接字)是否可读。如果可读,那就通过recvfrom函数接收UDP数据(基于UDP的RTP音视频数据),将接收到的数据存放到形参buf指向的缓冲区中:
static int rtp_read(URLContext *h, uint8_t *buf, int size)
{RTPContext *s = h->priv_data;int len, n, i;struct pollfd p[2] = {{s->rtp_fd, POLLIN, 0}, {s->rtcp_fd, POLLIN, 0}};int poll_delay = h->flags & AVIO_FLAG_NONBLOCK ? 0 : POLLING_TIME;struct sockaddr_storage *addrs[2] = { &s->last_rtp_source, &s->last_rtcp_source };socklen_t *addr_lens[2] = { &s->last_rtp_source_len, &s->last_rtcp_source_len };int runs = h->rw_timeout / 1000 / POLLING_TIME;for(;;) {if (ff_check_interrupt(&h->interrupt_callback))return AVERROR_EXIT;n = poll(p, 2, poll_delay);if (n > 0) {/* first try RTCP, then RTP */for (i = 1; i >= 0; i--) {if (!(p[i].revents & POLLIN))continue;*addr_lens[i] = sizeof(*addrs[i]);len = recvfrom(p[i].fd, buf, size, 0,(struct sockaddr *)addrs[i], addr_lens[i]);if (len < 0) {if (ff_neterrno() == AVERROR(EAGAIN) ||ff_neterrno() == AVERROR(EINTR))continue;return AVERROR(EIO);}if (ff_ip_check_source_lists(addrs[i], &s->filters))continue;return len;}} else if (n == 0 && h->rw_timeout > 0 && --runs <= 0) {return AVERROR(ETIMEDOUT);} else if (n < 0) {if (ff_neterrno() == AVERROR(EINTR))continue;return AVERROR(EIO);}if (h->flags & AVIO_FLAG_NONBLOCK)return AVERROR(EAGAIN);}
}
三、总结
1.从上面的代码分析可以看出来,接收端的FFmpeg接收RTP数据,其原理就是创建了一个UDP服务器来接收推流端发送的基于UDP的RTP数据,本质就是接收UDP数据。该UDP服务器的实现原理跟《Linux下使用poll函数编写UDP客户端、服务器程序》中展示的UDP服务器是一样的,都是调用了socket、bind、poll、recvfrom这几个函数。
2.跟普通的UDP服务器相比,FFmpeg接收RTP流时创建的UDP服务器,其端口来源于推流端的FFmpeg命令生成的SDP文件中的端口信息。也就是说此时的流程是推流端的UDP客户端先被创建,然后通过SDP把需要创建的UDP服务器的端口号发送给接收端,最后接收端才根据这个端口号创建UDP服务器。所以可以实现推流端先推流(UDP客户端先发送音视频数据,不管服务器有没有接收到),接收端再接收RTP音视频数据。
相关文章:
音视频入门基础:RTP专题(9)——FFmpeg接收RTP流的原理和内部实现
一、引言 由《音视频入门基础:RTP专题(2)——使用FFmpeg命令生成RTP流》可以知道,推流端通过下面FFmpeg命令可以将一个媒体文件转推RTP,生成RTP流: ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec cop…...
Nginx 安装及配置教程(Windows)【安装】
文章目录 一、 Nginx 下载 1. 官网下载2. 其它渠道 二、 Nginx 安装三、 配置四、 验证五、 其它问题 1. 常用命令2. 跨域问题 软件 / 环境安装及配置目录 一、 Nginx 下载 1. 官网下载 安装地址:https://nginx.org/en/download.html 打开浏览器输入网址 htt…...
《跟李沐学 AI》AlexNet论文逐段精读学习心得 | PyTorch 深度学习实战
前一篇文章,使用 AlexNet 实现图片分类 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 本篇文章内容来自于学习 9年后重读深度学习奠基作之一:AlexNet【下】【论文精读】】的心得。 《跟李沐…...
嵌入式0xDEADBEEF
在嵌入式系统中,0xDEADBEEF 是一个常见的“魔数”(magic number),通常用于调试和内存管理。它的含义和用途如下: 1. 调试用途 未初始化内存的标记:在调试时,0xDEADBEEF 常用于标记未初始化或已…...
B+树作为数据库索引结构的优势对比
MySQL作为数据库,它的功能就是做数据存储和数据查找;使用B树作为索引结构是为了实现高效的查找、插入和删除操作。 B树的查找、插入、删除的复杂度都为 O(log n),它是一个多叉树的结构,能兼顾各种操作的效率的数据结构。如果使用…...
自适应SQL计划管理(Adaptive SQL Plan Management)在Oracle 12c中的应用
在Oracle Database 12c Release 1 (12.1)版本中,引入了对SQL计划管理(SPM)功能的增强,特别是关于SQL计划基线的自动进化机制。这一改进允许数据库更加智能地管理和优化SQL查询的执行计划,确保即使数据分布发生变化&…...
什么是DeFi (去中心化金融)
DeFi (去中心化金融) 概述 💰 1. DeFi 基础概念 1.1 什么是 DeFi? DeFi 是建立在区块链上的金融服务生态系统,它: 无需中心化中介开放且透明无需许可即可参与代码即法律 1.2 DeFi 的优势 开放性:任何人都可以参与…...
计算机毕业设计Python农产品推荐系统 农产品爬虫 农产品可视化 农产品大数据(源码+LW文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
LLM论文笔记 15: Transformers Can Achieve Length Generalization But Not Robustly
Arxiv日期:2024.2.14机构:Google DeepMind / University of Toronto 关键词 长度泛化位置编码数据格式 核心结论 1. 实验结论:十进制加法任务上的长度泛化最佳组合: FIRE位置编码 随机化位置编码 反向数据格式 索引提示&…...
SpringAI做对了什么
开发|界面|引擎|交付|副驾——重写全栈法则:AI原生的倍速造应用流 你好,这里是nine[谈架构]系列。 欢迎关注评论私信交流~ SpringAI 在 AI 编程领域延续了Spring的诸多优势,从易于集成、到通用…...
DeepSeek预测25考研分数线
25考研分数马上要出了。 目前,多所大学已经陆续给出了分数查分时间,综合往年情况来看,每年的查分时间一般集中在2月底。 等待出成绩的日子,学子们的心情是万分焦急,小编用最近爆火的“活人感”十足的DeepSeek帮大家预…...
C++笔记之标准库中的std::copy 和 std::assign 作用于 std::vector
C++笔记之标准库中的std::copy 和 std::assign 作用于 std::vector code review! 文章目录 C++笔记之标准库中的std::copy 和 std::assign 作用于 std::vector1. `std::copy`1.1.用法1.2.示例2.`std::vector::assign`2.1.用法2.2.示例3.区别总结4.支持assign的容器和不支持ass…...
文件IO(20250217)
1. 文件IO 系统调用Linux内核提供的文件操作接口 1. 打开文件 open 2. 读写文件 read/write 3. 关闭文件 close 1.1 open函数 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int open(const char *pathname, int flags); int ope…...
Django5 实用指南(四)URL路由与视图函数
4.1 Django5的URL路由系统 Django 的 URL 路由系统是其核心组件之一,它负责将用户的 HTTP 请求(即 URL)映射到相应的视图函数上。每当用户在浏览器中访问某个 URL 时,Django 会根据项目的 URL 配置文件(urls.py&#…...
Android 14输入系统架构分析:图解源码从驱动层到应用层的完整传递链路
一、资料快车 1、深入了解Android输入系统:https://blog.csdn.net/innost/article/details/47660387 2、书籍 - Android系统源代码情景分析 二、Perface 1、参考: 2、系统程序分析方法 1)加入log,并跟着log一步步分析 -logc…...
Java中Map循环安全的删除数据的4中方法
文章目录 前言一、使用Iterator删除二、使用 removeIf(Java 8)三、遍历时记录需要删除的键(不推荐)四、使用 Stream(Java 8)总结 前言 在 Java 中,遍历 HashMap 并删除数据时,直接使…...
蓝桥杯(B组)-每日一题(1093字符逆序)
c中函数: reverse(首位置,尾位置) reverse(s.begin(),s.end()) 头文件:<algorithm> #include<iostream> #include<algorithm>//运用reverse函数的头文件 using namespace std; int main() {string s;//定义一…...
【数据分析】3 数据分析成长之路
职业发展路径: 向上发展(技术方向):可以详细说明成为数据科学家或专家所需的具体技能和步骤,包括学习的算法、工具等。向下发展(业务方向):可以探讨结合业务知识的具体领域ÿ…...
循环神经网络RNN原理与优化
目录 前言 RNN背景 RNN原理 上半部分:RNN结构及按时间线展开图 下半部分:RNN在不同时刻的网络连接和计算过程 LSTM RNN存在的问题 LSTM的结构与原理 数学表达层面 与RNN对比优势 应用场景拓展 从简易但严谨的代码来看RNN和LSTM RNN LSTM 前言 绕循环神经…...
Python正则表达式处理中日韩字符过滤全解析
Python正则表达式处理中日韩字符过滤全解析 一、核心原理:Unicode字符范围定位 中日韩字符在Unicode中的分布: 中文:\u4e00-\u9fff(基本区) \u3400-\u4dbf(扩展A区) \U00020000-\U0002a6df…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...
STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...
【记录坑点问题】IDEA运行:maven-resources-production:XX: OOM: Java heap space
问题:IDEA出现maven-resources-production:operation-service: java.lang.OutOfMemoryError: Java heap space 解决方案:将编译的堆内存增加一点 位置:设置setting-》构建菜单build-》编译器Complier...
比特币:固若金汤的数字堡垒与它的四道防线
第一道防线:机密信函——无法破解的哈希加密 将每一笔比特币交易比作一封在堡垒内部传递的机密信函。 解释“哈希”(Hashing)就是一种军事级的加密术(SHA-256),能将信函内容(交易细节…...
