当前位置: 首页 > article >正文

音视频入门基础: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音视频数据):

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流的原理和内部实现

一、引言 由《音视频入门基础&#xff1a;RTP专题&#xff08;2&#xff09;——使用FFmpeg命令生成RTP流》可以知道&#xff0c;推流端通过下面FFmpeg命令可以将一个媒体文件转推RTP&#xff0c;生成RTP流&#xff1a; ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec cop…...

STM32 串口转 虚拟串口---实现USB转串口功能

一&#xff0c;USART与UART 区别 USART&#xff08;Universal Synchronous/Asynchronous Receiver/Transmitter&#xff09;通用同步/异步串行接收/发送器 相较于UART&#xff1a;通用异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;多了…...

【进程与线程】Linux 线程、同步以及互斥

每个用户进程有自己的地址空间。 线程是操作系统与多线程编程的基础知识。 系统为每个用户进程创建一个 task_struct 来描述该进程&#xff1a;该结构体中包含了一个指针指向该进程的虚拟地址空间映射表&#xff1a; 实际上 task_struct 和地址空间映射表一起用来表示一个进程…...

胶囊网络动态路由算法:突破CNN空间局限性的数学原理与工程实践

一、CNN的空间局限性痛点解析 传统CNN的瓶颈&#xff1a; 池化操作导致空间信息丢失&#xff08;最大池化丢弃85%激活值&#xff09;无法建模层次空间关系&#xff08;旋转/平移等变换不敏感&#xff09;局部感受野限制全局特征整合 示例对比&#xff1a; # CNN最大池化示例…...

当pcie设备变化时centos是否会修改网络设备的名称(AI回答)

当pcie设备变化时centos是否会修改网络设备的名称 在CentOS&#xff08;以及其他基于Linux的操作系统&#xff09;中&#xff0c;网络接口的命名通常遵循特定的规则&#xff0c;尤其是在使用PCIe设备&#xff08;如网络适配器&#xff09;时。网络接口的命名通常基于设备的物理…...

【人工智能】释放数据潜能:使用Featuretools进行自动化特征工程

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 特征工程是机器学习流程中至关重要的一步,它直接影响模型的性能。然而,手动特征工程既耗时又需要领域专业知识。Featuretools是一个强大的…...

docker批量pull/save/load/tag/push镜像shell脚本

目录 注意&#xff1a; 脚本内容 执行效果 注意&#xff1a; 以下脚本为shell脚本通过docker/nerdctl进行镜像独立打包镜像的相关操作脚本内仓库信息和镜像存取路径需自行更改需自行创建images.txt并填写值&#xff0c;并且与脚本位于同级目录下 [rootmaster01 sulibao]# l…...

对正则表达式说不!!!

可能大家都会和我一样&#xff0c;时常会遇到正则表达式&#xff0c;有时候会忘记某些字符而苦恼。今天就帮助大家克服它&#xff0c;虽然不多&#xff0c;但我认为掌握这些足够了&#xff0c;万变不离其宗&#xff0c;以不变应万变。 一、正则表达式内容分类 1. 字符类 [abc…...

Redis日志分析

主从同步尝试&#xff1a; 日志中多次出现“Master is currently unable to PSYNC but should be in the future: -NOMASTERLINK Can’t SYNC while not connected with my master”。这表明从服务器尝试与主服务器进行部分重同步&#xff08;PSYNC&#xff09;&#xff0c;但由…...

【做一个微信小程序】校园地图页面实现

前言 上一个教程我们实现了小程序的一些的功能&#xff0c;有背景渐变色&#xff0c;发布功能有的呢&#xff0c;已支持图片上传功能&#xff0c;表情和投票功能开发中&#xff08;请期待&#xff09;。下面是一个更高级的微信小程序实现&#xff0c;包含以下功能&#xff1a;…...

Web后端 - Maven管理工具

一 Maven简单介绍 Maven是apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 Maven的作用 二 Maven 安装配置 依赖配置 依赖传递 依赖范围 生命周期 注意事项&#xff1a;在同一套生命周期中&#xff0c;当运行后面的阶段时&#xff0c;前面的阶段都…...

20250217-POMO笔记

文章目录 前言一、伪代码一&#xff1a;POMO Training二、伪代码二&#xff1a;POMO Inference三、POMO注意力模型3.1、自注意力机制3.2、AM模型 前言 以下主要讲解两个算法的伪代码以及注意力模型。 一、伪代码一&#xff1a;POMO Training POMO Training是POMO模型训练的伪…...

JavaEE-SpringBoot快速入门

文章目录 本节目标Maven什么是Maven创建一个Maven项目maven项目功能maven的依赖管理全球仓库, 私服, 本地服务器, 配置国内镜像 第一个SpringBoot项目创建项目运行SpringBoot程序 SpringBoot原理初步Web服务器 总结 本节目标 了解什么是maven, 配置国内源使用Springboot创建项…...

游戏引擎学习第107天

仓库:https://gitee.com/mrxiao_com/2d_game_2 回顾我们之前停留的位置 在这段内容中&#xff0c;讨论了如何处理游戏中的三维效果&#xff0c;特别是如何处理额外的“Z层”。由于游戏中的艺术资源是位图而不是3D模型&#xff0c;因此实现三维效果变得非常具有挑战性。虽然可…...

Sprinig源码解析

前言 Spring 框架是 Java 企业级开发的基石&#xff0c;其源码设计体现了模块化、扩展性和灵活性。以下从 IoC 容器、AOP 实现、核心模块和关键设计模式四个角度对 Spring 源码进行深度解析&#xff0c;帮助理解其底层机制。即使Spring会使用的人见得就能使用。 一、IoC 容器源…...

ComfyUI流程图生图原理详解

一、引言 ComfyUI 是一款功能强大的工具&#xff0c;在图像生成等领域有着广泛应用。本文补充一点ComfyUI 的安装与配置过程遇到的问题&#xff0c;并深入剖析图生图过程及相关参数&#xff0c;帮助读者快速入门并深入理解其原理。 二、ComfyUI 的安装与配置中遇到的问题 &a…...

使用右侧值现象来处理一个word导入登记表的需求

需求也简单&#xff0c;导word文件用户登记表&#xff0c;有各部门的十几个版本&#xff08;为什么这么多&#xff1f;不知道&#xff09;。这里说下谈下我的一些代码做法&#xff1a; 需求分析&#xff1a; 如果能解决java字段和各项填的值怎么配对的问题&#xff0c;那么就…...

《open3d pyqt》Alpha重建

《open3d pyqt》Alpha重建 一、效果展示二、qt设置2.1 主界面添加动作2.2 dialog 界面、布局如下:三、核心代码一、效果展示 二、qt设置 2.1 主界面添加动作 2.2 dialog 界面、布局如下: 并生成py文件,参考前述章节 三、核心代码 main.py文件增加 from Su...

深度解析HTTP/HTTPS协议:从原理到实践

深入浅出HTTP/HTTPS协议&#xff1a;从原理到实践 前言 在当今互联网世界中&#xff0c;HTTP和HTTPS协议如同空气般存在于每个网页请求的背后。作为开发者或技术爱好者&#xff0c;理解这些基础协议至关重要。本文将用六大板块&#xff0c;配合原理示意图和实操案例&#xff0…...

数据结构:顺序表(Sequence List)及其实现

什么是顺序表&#xff1f; 顺序表是一种最简单的数据结构&#xff0c;它就像一排连续的小房子&#xff0c;每个房子里都住着一个数据元素。这些房子是按顺序排列的&#xff0c;每个房子都有一个门牌号&#xff08;下标&#xff09;&#xff0c;我们可以通过门牌号快速找到对应…...

小程序canvas2d实现横版全屏和竖版逐字的签名组件(字帖式米字格签名组件)

文章标题 01 功能说明02 效果预览2.1 横版2.2 竖版 03 使用方式04 横向签名组件源码4.1 html 代码4.2 业务 Js4.3 样式 Css 05 竖向签名组件源码5.1 布局 Html5.2 业务 Js5.3 样式 Css 01 功能说明 技术栈&#xff1a;uniapp、vue、canvas 2d 需求&#xff1a; 实现横版的全…...

MoE演变过程

MoE演变过程 1 MoE1.1 BasicMoE1.2 SparseMoE1.2.1 实现 1.3 Shared Expert SparseMoE 1 MoE 参考&#xff1a;https://huggingface.co/blog/zh/moe 1.1 BasicMoE 用router给出各专家的权重&#xff0c;然后让输入过每一个专家&#xff0c;然后做加权求和。 1.2 SparseMoE …...

【Leetcode 热题 100】1287. 有序数组中出现次数超过25%的元素

问题背景 给你一个非递减的 有序 整数数组&#xff0c;已知这个数组中恰好有一个整数&#xff0c;它的出现次数超过数组元素总数的 25 % 25\% 25%。 请你找到并返回这个整数。 数据约束 1 ≤ a r r . l e n g t h ≤ 1 0 4 1 \le arr.length \le 10 ^ 4 1≤arr.length≤104 0…...

ruby 的安装

在51cto搜索的资料 ruby on rails的安装 http://developer.51cto.com/art/200906/129669.htm http://developer.51cto.com/art/200912/169391.htm http://developer.51cto.com/art/200908/147276.htm 史上最完整的ruby&#xff0c;rails环境架设配置&#xff08;Apachefast…...

【java】List<String> fruits = new ArrayList<>(); 这一句是什么

1. 代码分解 java Copy List<String> fruits new ArrayList<>(); List<String>&#xff1a; List 是 Java 中的一个接口&#xff0c;表示一个有序的集合&#xff08;可以重复元素&#xff09;。 <String> 是泛型&#xff0c;表示这个列表中的元素…...

通俗诠释 DeepSeek-V3 模型的 “671B” ,“37B”与 “128K”,用生活比喻帮你理解模型的秘密!

欢迎来到涛涛聊AI。 在DeepSeek-V3模型的参数描述中&#xff0c;你可能会看到类似“671B 37B 128K”这样的标记。这些字母和数字的组合看起来像密码&#xff0c;但其实它们揭示了模型的“大脑容量”和“工作方式”。我们用日常生活的比喻来解释&#xff1a; 一、数字含义&…...

【鸿蒙Next】优秀鸿蒙博客集锦

鸿蒙基础开发&#xff1a;多文件压缩上传及断点续传_鸿蒙 断点续传-CSDN博客...

【实战项目】BP神经网络识别人脸朝向----MATLAB实现

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…...

C++线程安全函数

在 C 中&#xff0c;线程安全的函数是指在多线程环境下可以安全调用&#xff0c;不会导致数据竞争或其他并发问题的函数。C 标准库提供了许多线程安全的函数&#xff0c;同时也要求开发者在使用自定义函数时确保线程安全。以下是一些常见的线程安全函数和实现线程安全的方法&am…...

Java中的分布式(概念说明)

1. 分布式的基本概念 1.1 什么是分布式系统&#xff1f; 分布式系统&#xff08;Distributed System&#xff09;&#xff1a;由多台服务器&#xff08;或节点&#xff09;协同工作&#xff0c;对外提供一个整体服务。不同节点之间通过网络通信来协同处理请求或共享数据&…...