GB28181 —— 5、C++编写GB28181设备端,完成将USB摄像头视频实时转发至GB28181服务并可播放(附源码)

news2024/11/22 17:41:01
被测试的USB摄像头

在这里插入图片描述

效果

源码说明

     主要功能模拟设备端,完成注册、注销、心跳等,同时当服务端下发指令播放视频时 设备端实时读取USB摄像头视频并通过OpenCV处理后实时转ps格式后封包rtp进行推送给服务端播放。

源码
/***
 *@remark:   pes头的封装,里面的具体数据的填写已经占位,可以参考标准
 *@param :   pData      [in] 填充ps头数据的地址
 *           stream_id  [in] 码流类型
 *           paylaod_len[in] 负载长度
 *           pts        [in] 时间戳
 *           dts        [in]
 *@return:   0 success, others failed
*/
int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, int64_t pts, int64_t dts)
{

	bits_buffer_t  	bitsBuffer;
	bitsBuffer.i_size = PES_HDR_LEN;
	bitsBuffer.i_data = 0;
	bitsBuffer.i_mask = 0x80;
	bitsBuffer.p_data = (unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, PES_HDR_LEN);

	bits_write(&bitsBuffer, 24, 0x000001);	//*start code*//*
	bits_write(&bitsBuffer, 8, (stream_id));	//*streamID*//*
	bits_write(&bitsBuffer, 16, (payload_len)+13);	//*packet_len*//* //指出pes分组中数据长度和该字节后的长度和
	bits_write(&bitsBuffer, 2, 2);		//*'10'*//*
	bits_write(&bitsBuffer, 2, 0);		//*scrambling_control*//*
	bits_write(&bitsBuffer, 1, 0);		//*priority*//*
	bits_write(&bitsBuffer, 1, 0);		//*data_alignment_indicator*//*
	bits_write(&bitsBuffer, 1, 0);		//*copyright*//*
	bits_write(&bitsBuffer, 1, 0);		//*original_or_copy*//*
	bits_write(&bitsBuffer, 1, 1);		//*PTS_flag*//*
	bits_write(&bitsBuffer, 1, 1);		//*DTS_flag*//*
	bits_write(&bitsBuffer, 1, 0);		//*ESCR_flag*//*
	bits_write(&bitsBuffer, 1, 0);		//*ES_rate_flag*//*
	bits_write(&bitsBuffer, 1, 0);		//*DSM_trick_mode_flag*//*
	bits_write(&bitsBuffer, 1, 0);		//*additional_copy_info_flag*//*
	bits_write(&bitsBuffer, 1, 0);		//*PES_CRC_flag*//*
	bits_write(&bitsBuffer, 1, 0);		//*PES_extension_flag*//*
	bits_write(&bitsBuffer, 8, 10);		//*header_data_length*//*
	// 指出包含在 PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前
	//的字节指出了有无可选字段。

	//*PTS,DTS  PTS DTS均为1的情况*//
	bits_write(&bitsBuffer, 4, 3);                    //*'0011'*//*
	bits_write(&bitsBuffer, 3, ((pts) >> 30) & 0x07);     //*PTS[32..30]*//*
	bits_write(&bitsBuffer, 1, 1);
	bits_write(&bitsBuffer, 15, ((pts) >> 15) & 0x7FFF);    //*PTS[29..15]*//*
	bits_write(&bitsBuffer, 1, 1);
	bits_write(&bitsBuffer, 15, (pts) & 0x7FFF);          //*PTS[14..0]*//*
	bits_write(&bitsBuffer, 1, 1);
	bits_write(&bitsBuffer, 4, 1);                    //*'0001'*//*
	bits_write(&bitsBuffer, 3, ((dts) >> 30) & 0x07);     //*DTS[32..30]*//*
	bits_write(&bitsBuffer, 1, 1);
	bits_write(&bitsBuffer, 15, ((dts) >> 15) & 0x7FFF);    //*DTS[29..15]*//*
	bits_write(&bitsBuffer, 1, 1);
	bits_write(&bitsBuffer, 15, (dts) & 0x7FFF);          //*DTS[14..0]*//*
	bits_write(&bitsBuffer, 1, 1);
	return 0;
}


/**
 * RTP头封装
 * @param pData buffer地址
 * @param seqNum 序号
 * @param timestamp 时间戳
 * @param ssrc 标识
 * @return
 */
int gb28181_make_rtp_header(char *pData, int seqNum, int64_t timestamp, int ssrc, int isEnd)
{

	bits_buffer_t  	bitsBuffer;
	bitsBuffer.i_size = RTP_HDR_LEN;
	bitsBuffer.i_data = 0;
	bitsBuffer.i_mask = 0x80;
	bitsBuffer.p_data = (unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, RTP_HDR_LEN);
	bits_write(&bitsBuffer, 2, 2);	    /*协议版本*/
	bits_write(&bitsBuffer, 1, 0);		/*P*/
	bits_write(&bitsBuffer, 1, 0);		/*X*/
	bits_write(&bitsBuffer, 4, 0);		/*CSRC个数*/
	bits_write(&bitsBuffer, 1, isEnd);			/*一帧是否结束*/
	bits_write(&bitsBuffer, 7, 96); 		/*载荷的数据类型*/
	bits_write(&bitsBuffer, 16, seqNum); 			/*序列号,第几个*/
	bits_write(&bitsBuffer, 32, timestamp);		/*时间戳,第一个 */
	bits_write(&bitsBuffer, 32, ssrc);			/*同步信源(SSRC)标识符*/
	return 0;
}

/***
 *@remark:   sys头的封装,里面的具体数据的填写已经占位,可以参考标准
 *@param :   pData  [in] 填充ps头数据的地址
 *@return:   0 success, others failed
*/
int32_t __gb28181_make_sys_header(char *pData, int32_t audioCnt, int32_t videoCnt, int32_t audioStreamID, int32_t videoStreamID)
{
	bits_buffer_t  	bitsBuffer;
	bitsBuffer.i_size = SYS_HDR_LEN;	// 18
	bitsBuffer.i_data = 0;
	bitsBuffer.i_mask = 0x80;
	bitsBuffer.p_data = (unsigned char *)(pData);
	memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);

	/*system header*/
	bits_write(&bitsBuffer, 32, 0x000001BB);	/*start code*/
	bits_write(&bitsBuffer, 16, SYS_HDR_LEN - SYS_BASE_HEAD_LEN); /*header_length 表示此字段后面的长度*/

	bits_write(&bitsBuffer, 1, 1);            /*marker_bit*/
	// bits_write(&bitsBuffer, 22, 3967);		/*rate_bound*/
	bits_write(&bitsBuffer, 22, 26234);	// 抓包读取
	bits_write(&bitsBuffer, 1, 1);            /*marker_bit*/

	bits_write(&bitsBuffer, 6, audioCnt);     /*audio_bound >= audio_stream_cnt*/

	bits_write(&bitsBuffer, 1, 0);            /*fixed_flag,1为固定比特率,0为可变比特率 */

	// bits_write(&bitsBuffer, 1, 1);        	/* CSPS_flag=1表示满足ps标准 */
	bits_write(&bitsBuffer, 1, 0);				// 抓包CSPS_flag=0

	bits_write(&bitsBuffer, 1, 1);        	/*system_audio_lock_flag*/

	bits_write(&bitsBuffer, 1, 1);        	/*system_video_lock_flag*/

	bits_write(&bitsBuffer, 1, 1);        	/*marker_bit*/

	bits_write(&bitsBuffer, 5, videoCnt);        	/*video_bound >= video_stream_cnt*/
	bits_write(&bitsBuffer, 1, 0);        	/*dif from mpeg1,if CSPS_flag=0,inavailable*/
	bits_write(&bitsBuffer, 7, 0x7F);     	/*reserver*/

	/*video stream bound*/
	bits_write(&bitsBuffer, 8, videoStreamID);         /*stream_id*/
	bits_write(&bitsBuffer, 2, 3);        	/*marker_bit */
	bits_write(&bitsBuffer, 1, 1);        	/*PSTD_buffer_bound_scale*/
	// bits_write(&bitsBuffer, 13, 2048);     	/*PSTD_buffer_size_bound*/
	bits_write(&bitsBuffer, 13, 232);	// 抓包为232

	/*audio stream bound*/
	bits_write(&bitsBuffer, 8, audioStreamID);         /*stream_id*/
	bits_write(&bitsBuffer, 2, 3);        	/*marker_bit */
	bits_write(&bitsBuffer, 1, 0);            /*PSTD_buffer_bound_scale*/
	// bits_write(&bitsBuffer, 13, 512);          /*PSTD_buffer_size_bound*/
	bits_write(&bitsBuffer, 13, 40);	// 抓包为40

	return 0;
}

// 推流线程
void Push_rtp_Stream()
{
	std::cout << "\n................ 进入rtp线程 ................" << std::endl;
	//std::this_thread::sleep_for(std::chrono::seconds(1));

	// 套接字初始化
	if (!BindSocket()) { std::cout << "\n!!!绑定套接字失败,无法发送数据!!!" << std::endl; return; }

	char ps_header[PS_HDR_LEN] = { 0 };
	char ps_system_header[SYS_HDR_LEN] = { 0 };
	char ps_map_header[PSM_HDR_LEN] = { 0 };
	char pes_header[PES_HDR_LEN] = { 0 };
	char rtp_header[RTP_HDR_LEN] = { 0 };
	int time_base = 90000;
	int fps = 20;
	int send_packet_interval = 1000 / fps;
	int interval = time_base / fps;
	long pts = 0;
	//char frame[1024 * 128];
	char *frame = new char[1024 * 1024]; memset(frame, 0, 1024 * 1024);
// 	int single_packet_max_length = 1400;
// 	char rtp_packet[RTP_HDR_LEN + 1400];
	int single_packet_max_length = 10000;
	char *rtp_packet = new char[RTP_HDR_LEN + 10000]; memset(frame, 0, RTP_HDR_LEN + 10000);
	// int ssrc = 0xffffffff;
	int rtp_seq = 0;

	//while (PushThreadFlag)
	{
		for (auto i = 0; i < nalu_vector.size(); i++)
		{
			auto nalu = nalu_vector.at(i);
			NaluType  type = nalu->type;
			int length = nalu->length;
			char * packet = nalu->packet;

			int index = 0;
			if (NALU_TYPE_IDR == type)
			{
				gb28181_make_ps_header(ps_header, pts);
				memcpy(frame, ps_header, PS_HDR_LEN);
				index += PS_HDR_LEN;

				gb28181_make_sys_header(ps_system_header, 0x3f);
				memcpy(frame + index, ps_system_header, SYS_HDR_LEN);
				index += SYS_HDR_LEN;

				gb28181_make_psm_header(ps_map_header);
				memcpy(frame + index, ps_map_header, PSM_HDR_LEN);
				index += PSM_HDR_LEN;
			}
			else
			{
				gb28181_make_ps_header(ps_header, pts);
				memcpy(frame, ps_header, PS_HDR_LEN);
				index += PS_HDR_LEN;
			}

			//封装pes
			gb28181_make_pes_header(pes_header, 0xe0, length, pts, pts);
			memcpy(frame + index, pes_header, PES_HDR_LEN);
			index += PES_HDR_LEN;
			memcpy(frame + index, packet, length);
			index += length;

			//组包rtp
			int rtp_packet_count = ((index - 1) / single_packet_max_length) + 1;

			for (int i = 0; i < rtp_packet_count; i++) 
			{
				gb28181_make_rtp_header(rtp_header, rtp_seq, pts, atoi(ssrc.c_str()), i == (rtp_packet_count - 1));

				int writed_count = single_packet_max_length;
				if ((i + 1)*single_packet_max_length > index)
				{
					writed_count = index - (i* single_packet_max_length);
				}

				//添加包长字节
				int rtp_start_index = 0;
				unsigned short rtp_packet_length = RTP_HDR_LEN + writed_count;
				if (rtp_protocol == "TCP/RTP/AVP") 
				{
					unsigned char packt_length_ary[2];
					packt_length_ary[0] = (rtp_packet_length >> 8) & 0xff;
					packt_length_ary[1] = rtp_packet_length & 0xff;
					memcpy(rtp_packet, packt_length_ary, 2);
					rtp_start_index = 2;
				}

				memcpy(rtp_packet + rtp_start_index, rtp_header, RTP_HDR_LEN);
				memcpy(rtp_packet + +rtp_start_index + RTP_HDR_LEN, frame + (i* single_packet_max_length), writed_count);
				rtp_seq++;

				if (PushThreadFlag)
				{
					send_network_packet(rtp_packet, rtp_start_index + rtp_packet_length);
				}
				else
				{
					if (nalu != nullptr) 
					{
						delete nalu;
						nalu = nullptr;
					}
					return;
				}
			}

			pts += interval;
			
			std::this_thread::sleep_for(std::chrono::milliseconds(send_packet_interval));
		}
	}
	std::cout << "Push_rtp_Stream - 视频文件发送结束..." << std::endl;
}

// 注册:参数true注册、false注销
bool RegisterToServer(const bool rFlag = true)
{
	// 分配eXosip上下文
	sip_context = eXosip_malloc();
	if (!sip_context) { std::cout << "eXosip_malloc fail!" << std::endl; return false; }
	std::cout << "完成 - 分配eXosip上下文" << std::endl;

	// 初始化eXosip上下文
	eXosip_lock(sip_context);
	if(eXosip_init(sip_context) != OSIP_SUCCESS) { std::cout << "eXosip_init fail!" << std::endl; eXosip_unlock(sip_context); return false; }
	eXosip_unlock(sip_context);
	std::cout << "完成 - 初始化eXosip上下文" << std::endl;

	/*
	eXosip_guess_localip
		描述:监听套接字
		参数:
			参数2: 17代表用户数据报协议-udp、6代表tcp
			参数5: 2代表IP协议系列
	*/
	eXosip_lock(sip_context);
	if (eXosip_listen_addr(sip_context, 6, nullptr, GbParameter.device.Port, 2, 0) != OSIP_SUCCESS) { std::cout << "eXosip_listen_addr fail!" << std::endl; eXosip_unlock(sip_context); return false; }
	eXosip_unlock(sip_context);
	std::cout << "完成 - 套接字监听" << std::endl;

	// 清除eXosip中存储的所有身份验证凭据
	eXosip_lock(sip_context);
	if (eXosip_clear_authentication_info(sip_context) != OSIP_SUCCESS) { std::cout << "eXosip_clear_authentication_info fail!" << std::endl; eXosip_unlock(sip_context); return false; }
	eXosip_unlock(sip_context);
	std::cout << "完成 - 清除eXosip中存储的所有身份验证凭据" << std::endl;

	/*
	eXosip_guess_localip
		描述:查找当前网络(具有默认路由的接口)
		参数:
			参数2: 2代表IP协议系列
	*/
	eXosip_lock(sip_context);
	if(eXosip_guess_localip(sip_context, 2, local_ip, 64) != OSIP_SUCCESS) { std::cout << "eXosip_guess_localip fail!" << std::endl; eXosip_unlock(sip_context); return false; }
	eXosip_unlock(sip_context);
	std::cout << "完成 - 查找当前网络:" << local_ip << std::endl;

	// 组合sip字符串
	from_sip = "sip:" + GbParameter.device.Id + "@" + local_ip + ":" + std::to_string(GbParameter.device.Port);
	std::string contact = "sip:" + GbParameter.device.Id + "@" + local_ip + ":" + std::to_string(GbParameter.device.Port);
	proxy_sip = "sip:" + GbParameter.server.Id + "@" + GbParameter.server.Ip + ":" + std::to_string(GbParameter.server.Port);

	// 生成初始REGISTER请求
	osip_message_t *register_message = nullptr;
	eXosip_lock(sip_context);
	int register_id = eXosip_register_build_initial_register
	(
		sip_context,
		from_sip.c_str(),
		proxy_sip.c_str(),
		contact.c_str(),
		rFlag?3600:0,	// 0为注销[当为0时,设备是注销;当大于0时(Expires最小值为3600),设备是注册]
		&register_message
	);
	eXosip_unlock(sip_context);
	if (!register_message) { std::cout << "eXosip_register_build_initial_register fail!" << std::endl; return false; }
	std::cout << "完成 - 生成初始REGISTER请求:\n\t" 
		<< "from_sip:" << from_sip << "\n\t"
		<< "proxy_sip:" << proxy_sip << "\n\t"
		<< "contact:" << contact << "\n\t"
		<< "register_id:" << register_id << "\n\t" << std::endl;

	// 发送对现有注册的REGISTER请求
	eXosip_lock(sip_context);
	if (eXosip_register_send_register(sip_context, register_id, register_message) != OSIP_SUCCESS) { std::cout << "eXosip_register_send_register fail!" << std::endl; eXosip_unlock(sip_context); return false; }
	eXosip_unlock(sip_context);
	std::cout << (rFlag ? "完成 - 发送注册REGISTER请求" : "完成 - 发送注销REGISTER请求......................") << std::endl;

	// 开启线程定时发送心跳
	static bool HeartbeatWorkFlag = true;
	if (HeartbeatWorkFlag)
	{
		HeartbeatWorkFlag = false;
		std::thread heartbeat_task_thread(HeartbeatWork);
		heartbeat_task_thread.detach();
	}

	return true;
}

下载完整源码

关注

笔者 - jxd

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

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

相关文章

ETH Gas 之 Base Fee Priority Fee

前情回顾 ETH网络 之 Gas EIP-1559 EIP-1559 EIP-1559是以太坊改进提案&#xff08;Ethereum Improvement Proposal&#xff09;&#xff0c;旨在改进以太坊的交易费用机制。该提案引入了一种新的交易费用模型&#xff0c;以提高交易费用的可预测性和网络的效率。我们本文各…

敏捷开发最佳实践:学习与改进维度实践案例之会诊式培养敏捷教练

自组织团队能够定期反思并采取针对性行动来提升人效&#xff0c;但2022年的敏捷调研发现&#xff0c;70%的中国企业在学习和改进方面仍停留在团队级。本节实践案例将分享“会诊式培养敏捷教练”的具体做法&#xff0c;突出了敏捷以人为本的学习和改进&#xff0c;强调了通过人员…

​HTTP与HTTPS:网络通信的安全卫士

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

【SAP-ABAP】CO01保存时错误DBSQL_DUPLICATE_KEY_ERROR

找到该表的主键OBJNR&#xff0c;事务代码SM56中查看当前缓冲到该key的号码段&#xff0c;事务代码SNRO修改对象名称OBJNR编号范围状态。 事务代码SM13查看数据更新记录

音频转换器哪个好?5个角度详细测评~

我们常常会用到音频转换器&#xff0c;比如因为平台和设备对某些格式的不兼容&#xff0c;需要进行格式转换&#xff1b;比如有些音频文件可能过大&#xff0c;需要转换为更高效&#xff1b;压缩格式以节省存储空间或加快传输速度&#xff1b;比如调整音频文件的比特率、采样率…

腾讯云轻量应用服务器CPU型号谁知道?

腾讯云轻量应用服务器CPU型号是什么&#xff1f;轻量服务器处理器主频&#xff1f;腾讯云服务器网txyfwq.com账号下的CPU处理器型号为2.5GHz主频的Intel(R) Xeon(R) Gold 6133 CPU和2.4GHz主频Intel(R) Xeon(R) CPU E5-26xx v4&#xff0c;腾讯云轻量应用服务器不支持指定底层物…

AMPQ和rabbitMQ

RabbitMQ 的 Channel、Connection、Queue 和 Exchange 都是按照 AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;标准实现的。 AMPQ的网络部分 AMQP没有使用HTTP&#xff0c;使用TCP自己实现了应用层协议。 AMQP实现了自己特有的网络帧格式。 一个Connection…

蓝桥杯 2023 省A 更小的数

主要思路&#xff1a; 输入一个长度为n的字符串&#xff0c;用二维数组dp[i][j]来记录子串[i, j]是否需要反转一次才能满足条件。使用动态规划自底向上地填充dp数组。根据问题的要求&#xff0c;需要考虑字符串的子串中字符的大小关系来判断是否需要反转。最后统计满足条件的子…

航空实时监控

1、从Kafka中读取飞机数据&#xff0c;并进行清洗 此步骤在前面的“使用Spark清洗统计业务数据并保存到数据库中”任务阶段应该已经完成。如果没有完成&#xff0c;请参考源代码自行完成。核心类主要有三个&#xff1a;SparkStreamingApplication类、SparkUtil类和MapManager类…

Cache缓存:HTTP缓存策略解析

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

处理器方法的返回值--返回对象Object

处理器方法也可以返回Object对象。这个Object可以是Integer&#xff0c;String&#xff0c;自定义对象&#xff0c; Map&#xff0c;List 等。但返回的对象不是作为逻辑视图出现的&#xff0c;而是作为直接在页面显示的数据出现的。 返回对象&#xff0c;需要使用ResponseBody注…

全面整理!机器学习常用的回归预测模型(表格数据)

文章目录 一、前言二、线性模型三、非线性模型 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 回归预测建模的核心是学习输入 X X X 到输出 y y y &#xff08;其中 y y y 是连续值向量&#xff09;的映射关系。条件期望 E ( Y ∣ X x…

C# Stable Diffusion using ONNX Runtime

C# Stable Diffusion using ONNX Runtime github地址&#xff1a;https://github.com/saddam213/OnnxStack Welcome to OnnxStack! OnnxStack transforms machine learning in .NET, Seamlessly integrating with ONNX Runtime and Microsoft ML, this library empowers you …

OpenAI CEO透露GPT-4表现“有点糟糕”;通义听悟音视频问答登场;Adobe整合AI功能助力3D设计创作

&#x1f989; AI新闻 &#x1f680; OpenAI CEO透露GPT-4表现“有点糟糕” 摘要&#xff1a;OpenAI的首席执行官Sam Altman在与Lex Fridman的访谈中表示&#xff0c;GPT-4的表现并不令人满意&#xff0c;认为其“有点糟糕”&#xff0c;同时对即将到来的GPT-5寄予厚望。Altm…

HarmonyOS NEXT应用开发之Web组件预览PDF文件实现案例

介绍 本案例通过Web组件实现预览本地PDF文件和预览网络PDF文件&#xff0c;代码为Tabs容器组件包含了两个独立的TabContent子组件&#xff0c;分别标示为预览本地PDF文件和预览网络PDF文件。每个子组件内部构建一个Web组件。第一个Web组件利用resource协议关联本地PDF文件路径…

uniapp——第3篇:自定义组件、组件间传数据

前提&#xff0c;建议先学会前端几大基础&#xff1a;HTML、CSS、JS、Ajax&#xff0c;还有一定要会Vue!&#xff08;Vue2\Vue3&#xff09;都要会&#xff01;&#xff01;&#xff01;不然不好懂 一、组件是啥玩意&#xff1f; 我之前讲vue2的文章讲过 Vue全家桶:vue2vue3全…

(css)步骤条el-steps区分等待、进行中、完成三种状态的图片

(css)步骤条el-steps区分等待、进行中、完成三种状态的图片 效果&#xff1a; <el-steps :active"active" finish-status"success" class"steps"><el-step title"选择.."></el-step><el-step title"..规则&…

Docker容器化技术(docker-compose示例:部署discuz论坛和wordpress博客,使用adminer管理数据库)

安装docker-compose [rootservice ~]# systemctl stop firewalld [rootservice ~]# setenforce 0 [rootservice ~]# systemctl start docker[rootservice ~]# wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64创建目录 [rootse…

【Leetcode】1969. 数组元素的最小非零乘积

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个正整数 p 。你有一个下标从 1 1 1 开始的数组 n u m s nums nums &#xff0c;这个数组包含范围 [ 1 , 2 p − 1 ] [1, 2^p - 1] [1,2p−1] 内所有整数的二进制形式&…

Python通过Ctypes调用C++类,实测有效

文章目录 前言创建vs dll工程添加外部库编辑代码编译测试参考 前言 在软件开发中&#xff0c;有时候需要Python与C相结合&#xff0c;以充分发挥两者的优势 。Python作为一种高级编程语言&#xff0c;具有简洁易读的特点&#xff0c;适用于快速开发和原型设计。而C则是一种性能…