【开源项目】基于RTP协议的H264播放器

news2024/9/22 1:02:38

基于RTP协议的H264播放器

  • 1. 概述
  • 2.工程
  • 3.测试
  • 4.小结

1. 概述

前面记录了一篇基于RTP协议的H264的推流器、接收器的实现过程,但是没有加上解码播放,这里记录一下如何实现解码和播放,也是在前面的基础之上实现的。前一篇的记录为【开源项目】基于RTP协议的H264码流发送器和接收器

在前文中,接收器将接收到的一系列数据包进行解析,并分成了一个个完整的帧,存储在内存之中。下面要将这些完整的帧进行解码成为yuv,并且播放。因此,需要添加解码和播放部分的代码。工程的代码结构为

在这里插入图片描述
工程的核心函数是udp_receive_packet(),这个函数的主要工作流程为:
(1)使用recvfrom()来接收数据流
(2)使用check_fragment()对数据流进行解析,并且拷贝到本地内存中
(3)使用h264_parse_packet()来解码所获取的数据,并且使用SDL进行播放

在处理接收的数据流时,我是按照一整个压缩帧进行存储的,例如存储一个完整的Intra帧或者一个完整的P帧,不包含后续帧的信息。然而,使用av_parser_parse2()进行分析时,会首先去寻找下一帧的起始地址来确定当前帧是否完整的输入了,如果没有找到,则很多分析流程不会执行。我在这里用了一个小技巧,在数据内存的最末尾加上一个伪起始码,让av_parser_parse2()确认已经接受了所有的数据,从而进行后续的分析

PS:不过其实这样写并不通用,只是为了配合我的整帧存储方式进行的微调。如果要实现通用的解码,在接收时去掉RTP的头,送入到av_parser_parse2()就可以了

2.工程

头文件的定义中,包括rtp header和rtp packet的定义,还定义了一个全局上下文信息结构体rtp_sdl_context_t

#pragma once

#include <stdio.h>
#include <WinSock2.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"
};

#undef main

#define RECV_DATA_SIZE			10000
#define MAX_BUFF_SIZE			32 * 1024 * 1024

#define RTP_MAX_PKT_SIZE        1400	// RTP数据包最大为
#define RTP_HEADER_SIZE			12
#define RTP_PADDING_SIZE		64

#define RTP_PACKET_START		1
#define RTP_PACKET_FRAGMENT		2
#define RTP_PACKET_END			3

#define RECV_STREAM_DOWNLOAD	0
#define RECV_YUV_DOWNLOAD		0

typedef struct rtp_header
{
	// 存储时高位存储的是version
	/* byte 0 */
	uint8_t csrc_len : 4;		/* expect 0 */
	uint8_t extension : 1;		/* expect 1 */
	uint8_t padding : 1;        /* expect 0 */
	uint8_t version : 2;        /* expect 2 */
	/* byte 1 */
	uint8_t payload_type : 7;
	uint8_t marker : 1;        /* expect 1 */
	/* bytes 2, 3 */
	uint16_t seq_num;
	/* bytes 4-7 */
	uint32_t timestamp;
	/* bytes 8-11 */
	uint32_t ssrc;            /* stream number is used here. */
}rtp_header_t;

typedef struct rtp_packet
{
	rtp_header_t rtp_h;
	uint8_t rtp_data[RTP_MAX_PKT_SIZE + RTP_PADDING_SIZE];
}rtp_packet_t;

typedef struct rtp_context
{
	int rtp_packet_cnt;
	int rtp_buffer_size;
	int rtp_frame_cnt;
	int packet_loc;				//
	uint8_t* rtp_buffer_data;
}rtp_context_t;

typedef struct rtp_sdl_context
{
	// video param
	const AVCodec* codec;
	AVCodecContext* codec_ctx;
	AVCodecParserContext* parser_ctx ;
	AVFrame* frame;
	SwsContext* img_convert_ctx;

	// SDL param
	SDL_Window* window;
	SDL_Renderer* render;
	SDL_Texture* texture;
	SDL_Rect rect;
}rtp_sdl_context_t;

cpp文件的定义和前文类似,只是增加了一些FFmpeg解码函数和SDL播放函数,重要部分有注释

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

#include "include/udp_rtp_decode_sdl.h"

FILE* fp_yuv;

//int avc_init(const AVCodec* codec, AVCodecContext* codec_ctx, AVCodecParserContext* parser, AVFrame* frame)
int avc_init(rtp_sdl_context_t* rsc)
{
	AVCodecID codec_id = AV_CODEC_ID_H264;
	rsc->codec = avcodec_find_decoder(codec_id);
	if (!rsc->codec)
	{
		printf("find decoder failed\n");
		return -1;
	}

	rsc->codec_ctx = avcodec_alloc_context3(rsc->codec);
	if (!rsc->codec_ctx)
	{
		printf("alloc context3 failed\n");
		return -1;
	}

	rsc->parser_ctx = av_parser_init(codec_id);
	if (!rsc->parser_ctx)
	{
		printf("parser ctx init failed\n");
		return -1;
	}

	rsc->frame = av_frame_alloc();
	if (!rsc->frame)
	{
		printf("alloc frame failed\n");
		return -1;
	}

	if (avcodec_open2(rsc->codec_ctx, rsc->codec, NULL) < 0)
	{
		printf("Could not open codec\n");
		return -1;
	}

	return 0;
}

int sdl_init(rtp_sdl_context_t* rsc)
{
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		printf("could not init sdl\n");
		return -1;
	}

	const int screen_w = 1280, screen_h = 720;
	const int pixel_w = 1280, pixel_h = 720;

	//SDL 2.0 Support for multiple windows
	rsc->window = SDL_CreateWindow("Play", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
		screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
	if (!rsc->window) {
		printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
		return -1;
	}
	rsc->render = SDL_CreateRenderer(rsc->window, -1, 0);

	int pixformat = SDL_PIXELFORMAT_IYUV;
	rsc->texture = SDL_CreateTexture(rsc->render, pixformat, SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);

	int border = 0;
	rsc->rect.x = 0 + border;
	rsc->rect.y = 0 + border;
	rsc->rect.w = screen_w - border * 2;
	rsc->rect.h = screen_h - border * 2;
}

void av_free_all(rtp_sdl_context_t* rsc)
{
	avcodec_free_context(&rsc->codec_ctx);
	av_parser_close(rsc->parser_ctx);
	av_frame_free(&rsc->frame);
}

int check_nalu_header(uint8_t data0)
{
	int forbidden_zero_bit = data0 & 0x80; // 1bit
	int nal_ref_idc = data0 & 0x60; // 2 bit
	int nal_unit_type = data0 & 0x1F; // 5bit
	if ((data0 & 0x80) == 1)
	{
		printf("forbidden zero bit should be 0\n");
		return -1;
	}
	// printf("forbidden_zero_bit:%d, nal_ref_idc:%d, nal_unit_type:%d\n", forbidden_zero_bit, nal_ref_idc, nal_unit_type);
	return nal_unit_type;
}

int check_fragment_nalu_header(rtp_context_t* rtp_ctx, uint8_t data0, uint8_t data1)
{
	int nal_unit_type = check_nalu_header(data0);
	int s, e, type;
	int pos;
	if (nal_unit_type == 28) // H264
	{
		s = data1 & 0x80; // S
		e = data1 & 0x40; // E
		type = data1 & 0x1F; // type

		pos = data1 & 0xC0; // 1100 0000
		switch (pos)
		{
		case 0x80:
			rtp_ctx->packet_loc = RTP_PACKET_START;
			break;
		case 0x40:
			rtp_ctx->packet_loc = RTP_PACKET_END;
			break;
		case 0x00:
			rtp_ctx->packet_loc = RTP_PACKET_FRAGMENT;
			break;
		default: // error
			printf("invalid packet loc\n");
			return -1;
			break;
		}
	}
	return 0;
}

int find_nal_unit(uint8_t* buf, int size, int* nal_start, int* nal_end)
{
	int i;
	// find start
	*nal_start = 0;
	*nal_end = 0;

	i = 0;
	while (   //( next_bits( 24 ) != 0x000001 && next_bits( 32 ) != 0x00000001 )
		(buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0x01) &&
		(buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0 || buf[i + 3] != 0x01)
		)
	{
		i++; // skip leading zero
		if (i + 4 >= size) { return 0; } // did not find nal start
	}

	if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0x01) // ( next_bits( 24 ) != 0x000001 )
	{
		i++;
	}

	if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0x01) { /* error, should never happen */ return 0; }
	i += 3;
	*nal_start = i;

	while (   //( next_bits( 24 ) != 0x000000 && next_bits( 24 ) != 0x000001 )
		(buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0) &&
		(buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0x01)
		)
	{
		i++;
		// FIXME the next line fails when reading a nal that ends exactly at the end of the data
		if (i + 3 >= size) { *nal_end = size; return -1; } // did not find nal end, stream ended first
	}

	*nal_end = i;
	return (*nal_end - *nal_start);
}

void set_default_rtp_context(rtp_context_t* rtp_ctx)
{
	memset(rtp_ctx->rtp_buffer_data, 0, sizeof(rtp_ctx->rtp_buffer_size));
	rtp_ctx->rtp_packet_cnt = 0;
	rtp_ctx->rtp_buffer_size = 0;
	rtp_ctx->packet_loc = 0;
}

// Check the data is fragment or not, if fragment, try to concate
int check_fragment(rtp_context_t* rtp_ctx, rtp_packet_t* rtp_pkt, uint8_t* data, int size)
{
	int nal_start, nal_end;
	int ret = 0;
	int data_size = size - RTP_HEADER_SIZE;
	find_nal_unit(data, data_size, &nal_start, &nal_end); // check NALU split pos

	uint8_t data0 = data[nal_start];
	uint8_t data1 = data[nal_start + 1];
	uint8_t fu_indicator, fu_header;

	if (nal_start > 0 && nal_start < 5) // single-fragment, maybe SPS, PPS or small size frame
	{
		fu_indicator = 0;
		fu_header = 0;
		ret = check_nalu_header(data0); // update nalu_type
		rtp_ctx->rtp_buffer_data = (uint8_t*)realloc(rtp_ctx->rtp_buffer_data, (rtp_ctx->rtp_buffer_size + data_size) * sizeof(uint8_t));
		memcpy(rtp_ctx->rtp_buffer_data + rtp_ctx->rtp_buffer_size, data, data_size);

#if STREAM_DOWNLOAD
		fwrite(rtp_ctx->rtp_buffer_data + rtp_ctx->rtp_buffer_size, 1, data_size, fp_in);
#endif

		fprintf(stdout, "rtp_ctx frame cnt:%d, frame_size:%d\n", rtp_ctx->rtp_frame_cnt, data_size);
		rtp_ctx->rtp_frame_cnt++;
		rtp_ctx->rtp_buffer_size += data_size;
	}
	else // multi-fragment
	{
		fu_indicator = data[0];
		fu_header = data[1];
		ret = check_fragment_nalu_header(rtp_ctx, fu_indicator, fu_header);
		if (ret < 0)
		{
			printf("invalid nalu header\n");
			return -1;
		}
		int real_data_size = data_size - 2;
		rtp_ctx->rtp_buffer_data = (uint8_t*)realloc(rtp_ctx->rtp_buffer_data, (rtp_ctx->rtp_buffer_size + real_data_size) * sizeof(uint8_t));
		if (!rtp_ctx->rtp_buffer_data)
		{
			printf("realloc rtp_buffer_data failed\n");
			return -1;
		}
		memcpy(rtp_ctx->rtp_buffer_data + rtp_ctx->rtp_buffer_size, data + 2, real_data_size); // plus 2 to skip fu_indicator and fu_header
#if STREAM_DOWNLOAD
		fwrite(rtp_ctx->rtp_buffer_data + rtp_ctx->rtp_buffer_size, 1, real_data_size, fp_in);
		fflush(fp_in);
#endif
		rtp_ctx->rtp_packet_cnt++;
		rtp_ctx->rtp_buffer_size += real_data_size;

		if (rtp_ctx->packet_loc == RTP_PACKET_END) // end of packet
		{
			fprintf(stdout, "rtp_ctx frame cnt:%d, frame_size:%d\n", rtp_ctx->rtp_frame_cnt, rtp_ctx->rtp_buffer_size);
			rtp_ctx->rtp_frame_cnt++;
		}
	}
	return 0;
}
// 伪造起始码
int forge_end_code(uint8_t* data, int size)
{
	data = (uint8_t*)realloc(data, (size + 6) * sizeof(uint8_t));
	if (!data)
	{
		printf("realloc end code failed\n");
		return -1;
	}

	data[size] = 0x00;
	data[size + 1] = 0x00;
	data[size + 2] = 0x00;
	data[size + 3] = 0x01;
	data[size + 4] = 0x41;
	data[size + 5] = 0x9A;
	size += 6;
	return size;
}

int h264_parse_packet(rtp_sdl_context_t* rsc, rtp_context_t* rtp_ctx, rtp_packet_t* rtp_pkt)
{
	AVPacket* packet;
	int ret = 0;
	
	packet = av_packet_alloc();
	if (!packet)
	{
		printf("alloc packet failed\n");
		return -1;
	}

	// 添加伪起始码
	uint8_t* buf_data = rtp_ctx->rtp_buffer_data;
	int data_size = rtp_ctx->rtp_buffer_size;
	data_size = forge_end_code(buf_data, data_size);

	ret = av_parser_parse2(rsc->parser_ctx, rsc->codec_ctx, &packet->data, &packet->size,
		buf_data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
	if (ret < 0) {
		printf("parse packet failed, err:%d\n", ret);
		return -1;
	}

	ret = avcodec_send_packet(rsc->codec_ctx, packet);
	if (ret < 0)
	{
		printf("send packet failed\n");
		return -1;
	}

	rsc->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	rsc->img_convert_ctx = sws_getContext(rsc->codec_ctx->width, rsc->codec_ctx->height, rsc->codec_ctx->pix_fmt,
		rsc->codec_ctx->width, rsc->codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

	while (ret >= 0) {
		ret = avcodec_receive_frame(rsc->codec_ctx, rsc->frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return -1;
		else if (ret < 0) {
			fprintf(stderr, "Error during decoding\n");
			exit(1);
		}

		//printf("saving frame:%d\n", rsc->codec_ctx->frame_num);
		fflush(stdout);

#if RECV_YUV_DOWNLOAD
		int size = rsc->frame->width * rsc->frame->height;
		fwrite(rsc->frame->data[0], 1, size, fp_yuv);//Y
		fwrite(rsc->frame->data[1], 1, size / 4, fp_yuv);//U
		fwrite(rsc->frame->data[2], 1, size / 4, fp_yuv);//V
		fflush(fp_yuv);
#endif

		sws_scale(rsc->img_convert_ctx, (const unsigned char* const*)rsc->frame->data, rsc->frame->linesize, 0, rsc->codec_ctx->height,
			rsc->frame->data, rsc->frame->linesize);
		// SDL播放
		SDL_UpdateYUVTexture(rsc->texture, &rsc->rect,
			rsc->frame->data[0], rsc->frame->linesize[0],
			rsc->frame->data[1], rsc->frame->linesize[1],
			rsc->frame->data[2], rsc->frame->linesize[2]);

		SDL_RenderClear(rsc->render);
		SDL_RenderCopy(rsc->render, rsc->texture, NULL, &rsc->rect);	
		SDL_RenderPresent(rsc->render);								
		SDL_Delay(40); // delay 40ms
	}
	return 0;
}
// 接收数据包
int udp_recevie_packet(rtp_sdl_context_t* rsc, const char* url, int port)
{
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 2);
	int cnt = 0;

	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		return 0;
	}

	SOCKET ser_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (ser_socket == INVALID_SOCKET)
	{
		ERROR("Invalid socket");
		return -1;
	}
	int on = 1;
	setsockopt(ser_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)& on, sizeof(on));

	sockaddr_in ser_addr;
	ser_addr.sin_family = AF_INET;
	ser_addr.sin_port = htons(port);
	ser_addr.sin_addr.s_addr = inet_addr(url);

	if (bind(ser_socket, (sockaddr*)& ser_addr, sizeof(ser_addr)) == SOCKET_ERROR)
	{
		printf("Bind socket addr error\n");
		closesocket(ser_socket);
		return -1;
	}
	sockaddr_in remote_addr;
	int addr_len = sizeof(remote_addr);

	char recv_data[RECV_DATA_SIZE];
	rtp_context_t* rtp_ctx = (rtp_context_t*)calloc(1, sizeof(rtp_context_t));
	if (!rtp_ctx)
	{
		printf("alloc rtp_ctx failed\n");
		return -1;
	}
	rtp_packet_t* rtp_pkt = (rtp_packet_t*)calloc(1, sizeof(rtp_packet_t));
	if (!rtp_pkt)
	{
		printf("alloc rtp_pkt failed\n");
		return -1;
	}

	fprintf(stdout, "Listening on port:%d\n", port);
	while (1)
	{
		// recvfrom接收传输过来的数据
		int pkt_size = recvfrom(ser_socket, recv_data, RECV_DATA_SIZE, 0, (sockaddr*)& remote_addr, &addr_len);
		if (pkt_size > 0)
		{
			memcpy(rtp_pkt, recv_data, pkt_size);
			check_fragment(rtp_ctx, rtp_pkt, rtp_pkt->rtp_data, pkt_size); // check pkt data is fragment or not
			rtp_header_t rtp_h = rtp_pkt->rtp_h;
			char payload = rtp_h.payload_type;

			if (rtp_ctx->packet_loc == RTP_PACKET_END)
			{
				switch (payload)
				{
				case 33: // mpegts
					// mpegts_packet_parse((uint8_t*)rtp_data, parse_mpegts, payload, rtp_data_size); // TODO: add mpegts parser
					printf("MPEGTS type\n");
					break;
				case 96: // h264
					//printf("payload type:%s\n", "H264");
					// 进行h264码流的解析
					h264_parse_packet(rsc, rtp_ctx, rtp_pkt);
					break;
				default:
					printf("Unknown type\n");
					break;
				}
				// printf("[RTP PKT] %5d| %5s | %10u| %5d| %5d\n", cnt, payload_str, timestamp, seq_num, pkt_size);
				set_default_rtp_context(rtp_ctx); // set default rtp ctx value
			}
		}
	}
}

int main()
{
	rtp_sdl_context_t* rsc = (rtp_sdl_context_t*)malloc(sizeof(rtp_sdl_context_t));
	if (!rsc)
	{
		printf("malloc rsc failed\n");
		return -1;
	}
	memset(rsc, 0, sizeof(rtp_sdl_context_t));

	// 初始化参数
	avc_init(rsc);
	sdl_init(rsc);
	
	// 如果要存储yuv信息可以设置为1
#if RECV_YUV_DOWNLOAD
	fp_yuv = fopen("rtp_receive_yuv.yuv", "wb");
#endif
	// 开始接收数据包
	udp_recevie_packet(rsc, "127.0.0.1", 8880);
	av_free_all(rsc);

#if RECV_YUV_DOWNLOAD
	fclose(fp_yuv);
#endif

	return 0;
}

3.测试

发送端
在这里插入图片描述
接收端
在这里插入图片描述
接收端播放正常,感觉可以后续改一改SDL的逻辑,让窗口变成可移动和可缩放的
在这里插入图片描述

4.小结

总体来说,这个功能的实现是比较简单的,不过使用了一个小的技巧,伪造了一个起始地址,如果代码格式要求不严格,可以凑合着用。如果要做成大的工程,应该将多个packet直接送入解码器,这样比较合理,也更符合FFmpeg的设计原则

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

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

相关文章

完成QT上位机(八)

一. 正式开始设计界面 这一章节我们将完成QT上位机的设计&#xff0c;如果有同学对QtCreater的使用不太熟悉的&#xff0c;可以参考下面的链接 Qt 快速入门系列教程 Qt 快速入门系列教程 (gitbooks.io)https://wizardforcel.gitbooks.io/qt-beginning/content/ 二. 数据库处…

从零开始的MicroPython(五)PWM

上一篇&#xff1a;串口 文章目录 定义ESP32代码 定义 要理解一个定义&#xff0c;就要引申一个已经掌握的概念&#xff0c;PWM是什么&#xff1f;我们或许可以用“周期”的概念来理解。 高电平出现的占比是占空比。 PWM&#xff08;脉冲宽度调制&#xff09;是一种调节信号…

022.(附加)chromedriver编译-绕过selenium机器人检测

有小伙伴说使用selenium没能绕过机器人检测&#xff0c;盘他。 一、selenium简介 Selenium 是一个强大的工具&#xff0c;用于Web浏览器自动化&#xff0c;更常被用于爬虫但selenium需要通过webdriver来驱动chrome&#xff0c;每次运行selenium时&#xff0c;都要先找到对应版…

VMware Workstation17 安装 Ubuntu 操作系统

今天给伙伴们分享一下VMware Workstation17 安装 Ubuntu 操作系统 &#xff0c;希望看了有所收获。 我是公众号「想吃西红柿」「云原生运维实战派」作者&#xff0c;对云原生运维感兴趣&#xff0c;也保持时刻学习&#xff0c;后续会分享工作中用到的运维技术&#xff0c;在运维…

企业邮箱有哪些便捷功能

企业邮箱有哪些便捷功能?一、消息流实现社交式互动&#xff1b;二、邮件委托轻松分担工作&#xff1b;三、音视频通话即时沟通&#xff1b;四、邮件定时发送与提醒确保重要信息不遗漏&#xff1b;五、邮件召回与延迟发送提供反悔机会&#xff1b;六、离线阅读邮件实现无缝工作…

QtQuick Text-对齐方式

属性 Text项目 的horizontalAlignment和verticalAlignment分别用来设置文本在 Text项目区域中的水平、垂直对齐方式。 默认文本在左上方。 属性值有&#xff1a; horizontalAlignment Text.AlignLeftText.AlignRightText.AlignHCenterText.Justify verticalAlignment Text.…

(纯分享01)初学AI,怎样才算是有效提示问题呢?

前言 你有没有想过&#xff0c;为什么有些人似乎能从 AI 工具中获得惊人的效果&#xff0c;而其他人却举步维艰&#xff1f;好用的永远在别人那&#xff0c;而自己的人工智能AI怎么这么像"人工智障"呢&#xff1f;有没有经常被气到呢哈哈哈哈&#xff1f; 问题的答…

Python IDLE修改JetBrains Mono字体教程

自己在使用Python IDLE过程中发现原生字体不好看&#xff0c;不美观。尤其是对于部分字符&#xff0c;l打印不美观&#xff0c;区别不明显。于是诞生了换字体的想法。 教程简单&#xff0c;快速&#xff0c;3-5分钟不到即可完成。 目录 选型 下载安装 使用 选型 考虑到代码…

网络空间资产测绘:为安全防护“画出”实时“地图”

网络空间已成为继海、陆、空、天之后的“第五疆域”&#xff0c;对其空间布局进行摸排并形成“地图”&#xff0c;是维护网络空间安全的基础性工作。近日在2024全球数字经济大会上发布的DayDayMap全球网络空间资产测绘平台&#xff0c;能为用户提供全面、精准、实时的全球网络空…

Go语言中获取tls中的KeyLogFile,用于dubug

文章目录 获取KeyLogFile示例代码&#xff1a;1. client2. client3. 效果 获取KeyLogFile tls.config自带了接口&#xff0c;所以配置的时候只需要打开就行&#xff0c;以客户端为例 keylogfile 是一个 io.Writer 开了这个就自动使用了 keyLogFile, _ : os.OpenFile(keyLogFi…

C#开发编程软件下载安装

1、Visual Studio 2022社区版下载 2、开始安装 3、安装进行中 。。。。

基于edge和bwmorph函数的两种图像边缘检测方法及应用

边缘检测是图像处理和计算机视觉中的基本问题&#xff0c;边缘检测的目的是标识数字图像中亮度变化明显的点。本文给出edge和bwmorph两个函数进行边缘检测的基本用法&#xff0c;并给出一个应用示例。 一、edge和bwmorph函数简介 edge和bwmorph是MATLAB中用于图像处理边缘检测…

Datawhale AI 夏令营(2024第三期)AI+逻辑推理方向 模型微调学习笔记

如何基于开源大模型进行优化 1. Prompt工程 大模型可能知道问题相关&#xff0c;但是我们问的不清楚。所以需要根据我们的提问&#xff0c;构建出一个比较结构化的、大模型易于理解和分析的提问内容。 在下方的第二个资料里&#xff0c;我才知道有这么多Prompt的构建思路&…

认真学习JVM中类加载过程

本文我们总结JVM中类加载器子系统关于类加载过程&#xff0c;这里默认是Oracle的Hotspot。 【1】类加载器子系统作用 类加载器子系统负责从文件系统或者网络中加载Class文件&#xff0c;class文件在文件开头有特定的文件标识。 ClassLoader只负责class文件的加载&#xff0…

软件测试——测试分类(超超超齐全版)

为什么要对软件测试进行分类 软件测试是软件⽣命周期中的⼀个重要环节&#xff0c;具有较⾼的复杂性&#xff0c;对于软件测试&#xff0c;可以从不同的⻆度加以分类&#xff0c;使开发者在软件开发过程中的不同层次、不同阶段对测试⼯作进⾏更好的执⾏和管理测试的分类⽅法。…

见证中国数据库的崛起:从追赶到引领的壮丽征程《一》

见证中国数据库的崛起&#xff1a;从追赶到引领的壮丽征程《一》 一、追溯历史&#xff1a;中国数据库发展的艰难起步萌芽阶段&#xff08;20世纪70年代末-80年代初&#xff09;起步阶段&#xff08;20世纪80年代中期-90年代初&#xff09;发展阶段&#xff08;20世纪90年代中期…

实验2-1-7 输出倒三角图案

本题要求编写程序&#xff0c;输出指定的由“*”组成的倒三角图案。 输出格式: * * * ** * ** **程序&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {for (int i 0; i < 4; i) {for (int k 0; k < i; k) {printf(" &qu…

YOLO:目标检测模型的训练和推理(简单Demo)

作者&#xff1a;CSDN _养乐多_ 本文将介绍如何训练和推理YOLO模型。使用coco8数据集以一个简单的demo进行示例。 文章目录 一、准备1.1 模型类型1.2 环境配置 二、模型训练和推理三、讨论 一、准备 1.1 模型类型 YOLO8模型性能数据&#xff1a; ModelSize (pixels)mAPval…

ICC2:分段长tree简易版教程

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 分段长tree让一部分sink balance的更好,有助于时序收敛,但ICC2的分段长tree需要单独写一个sdc去做sub tree再换回原始sdc去长tree,流程繁琐,我整理了一个简单的流程,不需要额外的sdc,唯一的缺…

Linux系统如何查看版本信息,内核、发行版、cpu、所有版本

查看当前操作系统内核信息&#xff1a;uname -a查看当前操作系统版本信息&#xff1a;cat /proc/version查看当前操作系统发行版信息&#xff1a; cat /etc/redhat-release 或 cat /etc/issue查看cpu相关信息&#xff0c;包括型号、主频、内核信息等&#xff1a;cat /proc/cpui…