FFMPEG之H264获取NALU并且解析其RBSP类型03

2023-11-18

FFMPEG之H264获取NALU并且解析其RBSP类型03

前言

FFMPEG之H264理论篇
理论的就不多讲了,可以参考上面那篇文章,下面将给出两种版本,一种是我自己的,用C++方法实现,另一种是雷神版本的,基本是纯C语言。区别是我多了一个将EBSP转成RBSP的函数,而雷神只是简单的将码流数据转成NALU后,将头部信息的优先级、NALU的类型和统计每一个NALU的字节大小LEN。并且都是经过测试没有bug(针对于我),如果有的话大家可以指出共同研究。 我的版本注释很清楚了,而雷神版本的有空再把注释打一下换上来。

1 C++版本实现获取NALU及解析内部数据

本来两个都想封装成C++类的,但是为了方便大家理解,就直接写了。

#pragma warning(disable:4996)

#include <iostream>
#include <cassert>
#include <vector>
using namespace std;//以后多文件操作尽量少用,容易改变命名空间规则出错

typedef unsigned char uint_8;


/*//
				下面实现了H264->NALU->RBSP(原始字节序列载荷)
				最好自己有空封装成一个类
/*//


//从H264获取NALU单元
//0-读完 1,2-没读完
int Find_NALU(FILE *fp, vector<uint_8> &nalu) {

	//1 每次读新的NALU都清除上一次的NALU数据
	nalu.clear();

	//用于检测起始码
	uint_8 buf[3] = { 0 };
	/*即:
	四字节起始码时:
	[0][1][2]={0,0,0}->[1][2][0]={0,0,0}->[2][0][1]={0,0,0}
	fgetc()==1
	三字节时:
	[0][1][2]={0,0,0}->[1][2][0]={0,0,0}->[2][0][1]={0,0,0}
	*/

	//2 先读三个字节判断
	for (int i = 0; i < 3; i++) {
		buf[i] = fgetc(fp);
		nalu.push_back(buf[i]);//必须放进去,防止buf不是0 0 0而丢失NALU数据
	}

	uint_8 byte;//用于存储每次读NALU数据的值
	int getPrefix = 0;//用于记录返回值.0-NALU读完;1,2-有NALU数据,实际区别不大
	int pos = 0;//当前文件指针位置,用于循环判断起始码

	//3 循环判断并取数据
	while (!feof(fp)) {
		//上面看不懂可看这里
		//0,1,2下标不为0->pos++后->pos=1,取余3后下标变成1,2,0->也不为0->pos=2->2,0,1...以此类推
		if (buf[pos % 3] == 0 && buf[(pos + 1) % 3] == 0 && buf[(pos + 2) % 3] == 1) {
			//found 00 00 01
			nalu.pop_back();//起始码不属于NALU数据
			nalu.pop_back();
			nalu.pop_back();
			getPrefix = 1;
			break;
		}
		else if (buf[pos % 3] == 0 && buf[(pos + 1) % 3] == 0 && buf[(pos + 2) % 3] == 0) {
			uint_8 tmp = fgetc(fp);
			if (tmp == 1) {
				//found 00 00 00 01
				nalu.pop_back();//起始码不属于NALU数据
				nalu.pop_back();
				nalu.pop_back();
				getPrefix = 2;
				break;
			}
			else {
				nalu.push_back(tmp); //这一字节也是NALU数据 别忘了!!!
			}
		}
		//否则说明不是起始码,继续读数据放对应取余下标(即每次取余后都是放在一个下标,不懂看上面的),并且读NALU数据进vector
		else {
			byte = fgetc(fp);
			//buf[(pos++) % 3] = byte; 一样
			buf[pos % 3] = byte;
			pos++;
			nalu.push_back(byte);  //因为这里一开始什么数据都读进,所以上面判断是起始码必须pop掉最后的
		}
	}

	return getPrefix;
}

void SwicthType(uint_8 type) {
	switch (type)
	{
	case 0:
		printf("未使用 Type.\n");
		break;
	case 1:
		printf("非IDR片 Type.\n");
		break;
	case 2:
		printf("片分区A Type.\n");
		break;
	case 3:
		printf("片分区B Type.\n");
		break;
	case 4:
		printf("片分区C Type.\n");
		break;
	case 5:
		printf("IDR Type.\n"); //I帧
		break;
	case 6:
		printf("SEI Type.\n"); //补充增强信息单元
		break;
	case 7:
		printf("SPS Type.\n");//序列参数集
		break;
	case 8:
		printf("PPS Type.\n");//图像参数集
		break;
	case 9:
		printf("分界符 Type.\n");
		break;
	case 10:
		printf("序列结束 Type.\n");
		break;
	case 11:
		printf("码流结束 Type.\n");
		break;
	case 12:
		printf("填充 Type.\n");
		break;
	default:
		printf("Unkonw.\n");
		break;
	}
}

//取出EBSP中的0x3,成为RBSP.即拓客操作
void Ebsp_To_Rbsp(vector<uint_8> &nalu) {

	//因为0 0 3必须最少三个数
	if (nalu.size() < 3) {
		return;
	}
	for (vector<uint_8>::iterator it = nalu.begin() + 2; it != nalu.end();) {
		if (*it == 3 && *(it - 1) == 0 && *(it - 2) == 0) {
			it = nalu.erase(it);
		}
		else {
			it++;
		}
	}
}

//利用上面的函数进行获取NALU的类型数据并进行解析为RBSP
int Parser_H264_Nalu(FILE *fp,vector<uint_8> &nalu) {

	//返回0表示解析完h264文件的NALU
	int ret = 0;
	//用于测试与雷神版本是否一致无错,统计每次的nalu总个数
	int cnt = 0;

	uint_8 typeNalu;
	do
	{
		ret = Find_NALU(fp, nalu);

		//首个NALU不做处理
		if (nalu.size() != 0) {
			//typeNalu = nalu.front() & 0x1f;
			typeNalu = nalu[0] & 0x1f;//同上
			SwicthType(typeNalu);
			Ebsp_To_Rbsp(nalu);
			cnt++;
		}
	} while (ret != 0);

	cout << cnt << endl;

	return 0;
}


void test01() {

	char fileName[100] = "sintel.h264";

	FILE *fp = fopen(fileName, "rb+");
	assert(fp);

	//用于存储NALU.注:读首个NALU时,vector大小为0,因为起始码一开始就满足,被读进三个又pop掉三个,只有第二个开始采用数据
	vector<uint_8> nalu;

	Find_NALU(fp, nalu);
	Find_NALU(fp, nalu);
	Find_NALU(fp, nalu);
	Find_NALU(fp, nalu);

	cout << "NALU元素个数为:" << nalu.size() << endl;
	for (vector<uint_8>::iterator it = nalu.begin(); it != nalu.end(); it++) {
		printf("%x ", *it);
	}

	fclose(fp);
}

void test02() {

	char fileName[100] = "sintel.h264";

	FILE *fp = fopen(fileName, "rb+");
	assert(fp);

	//解析NALU类型
	vector<uint_8> nalu;
	Parser_H264_Nalu(fp, nalu);

	fclose(fp);
}

int main() {

	//test01()

	test02();

	return 0;
}

2 雷神版本实现获取NALU及打印头部相关信息

当然,你学习了之后你可以改成你想要的方式解析内部数据。前面的也一样。

#pragma warning(disable:4996)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/*//
				雷霄骅版本
				下面实现了H264->NALU->RBSP(原始字节序列载荷)
				最好自己有空封装成一个类
/*//


//NALU的类型,由NALU头部的后5位得出
typedef enum {
	NALU_TYPE_SLICE = 1,
	NALU_TYPE_DPA = 2,
	NALU_TYPE_DPB = 3,
	NALU_TYPE_DPC = 4,
	NALU_TYPE_IDR = 5,
	NALU_TYPE_SEI = 6,
	NALU_TYPE_SPS = 7,
	NALU_TYPE_PPS = 8,
	NALU_TYPE_AUD = 9,
	NALU_TYPE_EOSEQ = 10,
	NALU_TYPE_EOSTREAM = 11,
	NALU_TYPE_FILL = 12,
} NaluType;

//IDC,NALU的优先级,NALU头部的第2、3位得出
typedef enum {
	NALU_PRIORITY_DISPOSABLE = 0,
	NALU_PRIRITY_LOW = 1,
	NALU_PRIORITY_HIGH = 2,
	NALU_PRIORITY_HIGHEST = 3
} NaluPriority;


//雷神自定义的结构体,用于保存开始码长度、NALU数据的长度(包括首字节)、临时缓冲区及其大小、头部信息
typedef struct
{
	int startcodeprefix_len;      //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
	int forbidden_bit;
	int nal_reference_idc;
	int nal_unit_type;
	unsigned len;                 //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
	unsigned max_size;            //! Nal Unit Buffer size
	char *buf;                    //! contains the first byte followed by the EBSP
} NALU_t;


//H264码流文件
FILE *h264bitstream = NULL;
//用于判断是否是起始码
int info2 = 0, info3 = 0;

//0不是起始码;1是起始码
static int FindStartCode2(unsigned char *Buf) {
	if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 1) return 0; //0x000001?
	else return 1;
}

//0不是起始码;1是起始码
static int FindStartCode3(unsigned char *Buf) {
	if (Buf[0] != 0 || Buf[1] != 0 || Buf[2] != 0 || Buf[3] != 1) return 0;//0x00000001?
	else return 1;
}


//获取码流中的NALU数据
//返回-1代表没找到起始码;0代表读文件出错或者calloc出错;其它返回带起始码的nalu长度,但是参数nalu->len是记录着不带起始码的长度
int GetAnnexbNALU(NALU_t *nalu) {

	//准备环节
	int pos = 0;//NALU的下标
	int StartCodeFound;//用于标记是否找到下一NALU的起始码
	int rewind;//下一起始码的位数,pos+rewind为下一起始码的首字节

	unsigned char *Buf;//临时缓存,用于每次存储一个NALU
	if ((Buf = (unsigned char*)calloc(nalu->max_size, sizeof(char))) == NULL) {
		printf("GetAnnexbNALU: Could not allocate Buf memory\n");
		return 0;
	}

	//假设起始码为三个字节长度
	nalu->startcodeprefix_len = 3;

	//1 先从码流中读取三个字节
	if (3 != fread(Buf, 1, 3, h264bitstream)) {
		free(Buf);
		return 0;
	}
	//2 判断是否满足00 00 01
	info2 = FindStartCode2(Buf);
	//3 如果不满足的话,再读一个进buf判断是否为00 00 00 01
	if (info2 != 1) {
		if (1 != fread(Buf + 3, 1, 1, h264bitstream)) {
			free(Buf);
			return 0;
		}
		info3 = FindStartCode3(Buf);
		//4 也不是00 00 00 01的话,则退出本次查找;否则记录NALU开始的下标与对应起始码长度
		if (info3 != 1) {
			free(Buf);
			return -1;
		}
		else {
			pos = 4;
			nalu->startcodeprefix_len = 4;
		}
	}
	//5 满足三字节的起始码,记录NALU开始的下标与对应起始码长度
	else {
		nalu->startcodeprefix_len = 3;
		pos = 3;
	}

	//来到这里说明找到了首个起始码,开始循环读NALU数据与找起始码(pos指向NALU的第一个字节数据)

	//6 先重置这些是否找到起始码的标志位
	StartCodeFound = 0;
	info2 = 0;
	info3 = 0;

	while (!StartCodeFound) {
		//15 由于最后一个NALU没有下一个起始码,所以当读到末尾时,直接将pos-1后减去起始码就是数据的长度
		//非0表示文件尾,0不是
		if (feof(h264bitstream) != 0) {
			nalu->len = (pos - 1) - nalu->startcodeprefix_len;//最后一个NALU数据的长度(减1是因为最后一个nalu时,会一个个读到buf,pos会自增,直到读到eof)
			memcpy(nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);//该NALU数据拷贝至该结构体成员buf中保存
			nalu->forbidden_bit = nalu->buf[0] & 0x80; //用该NALU的头部数据给赋给雷神自定义的NALU结构体中
			nalu->nal_reference_idc = nalu->buf[0] & 0x60; 
			nalu->nal_unit_type = (nalu->buf[0]) & 0x1f;
			free(Buf);			//每次获取完一个NALU都清空该NALU的数据(下面的获取也一样)
			return pos - 1;		//返回文件最后一个字节的下标,在最后一帧pos代表eof(并不是end,pos下标才对应eof)
			//return nalu->len;	// 这样返回才是不带起始码长度,上面pos-1是带的
		}
		//7 往Buf一字节一字节的读数据(注:pos之前有起始码,所以pos开始存,然后pos自增1)
		Buf[pos++] = fgetc(h264bitstream);
		//8 判断是否为四位起始码.例如0 0 0 1 2(实际上pos=5),此时从0 0 1 2开始判断,所以取Buf[pos - 4]元素开始判断
		info3 = FindStartCode3(&Buf[pos - 4]);
		if (info3 != 1) {
			//9 不是则判断是否是三位起始码.当前下标减去3即可(本来减2,但上面pos++了)
			info2 = FindStartCode2(&Buf[pos - 3]);
		}
		//10 若找到下一个起始码则退出(证明知道了一个NALU数据的长度嘛)
		StartCodeFound = (info2 == 1 || info3 == 1);
	}

	//11 判断下一个起始码是3还是4(他这里用info3,代表4位,所以一会需要将文件指针回调rewind个字节,为了下一次判断)
	//当然你也可以用info2
	rewind = (info3 == 1) ? -4 : -3;

	//12 回调文件指针
	if (0 != fseek(h264bitstream, rewind, SEEK_CUR)) {
		free(Buf);
		printf("GetAnnexbNALU: Cannot fseek in the bit stream file");
	}
	
	//13 开始获取NALU的数据
	nalu->len = (pos + rewind) - nalu->startcodeprefix_len;//注:rewind为负数,加相当于减,然后再减去上一起始码就是NALU的数据长度
	memcpy(nalu->buf, &Buf[nalu->startcodeprefix_len], nalu->len);//从起始码开始拷贝len长度的NALU数据至自定义结构体的buf中
	nalu->forbidden_bit = nalu->buf[0] & 0x80; //用该NALU的头部数据给赋给雷神自定义的NALU结构体中
	nalu->nal_reference_idc = nalu->buf[0] & 0x60; 
	nalu->nal_unit_type = (nalu->buf[0]) & 0x1f;
	free(Buf);

	//14 返回当前文件指针位置,即下一起始码的首字节(rewind为负数)  至此,一个NALU的数据获取完毕
	return (pos + rewind);
}

/*
	解析码流h264
	成功返回0;失败返回-1
 */
int simplest_h264_parser(char *url) {
	
	//FILE *myout=fopen("output_log.txt","wb+");
	FILE *myout = stdout;//用于输出屏幕的文件指针,你可以认为该文件是屏幕

	//1 打开文件
	h264bitstream = fopen(url, "rb+");
	if (h264bitstream == NULL) {
		printf("Open file error\n");
		return -1;
	}

	//2 开辟nalu结构体以及用于存储nalu数据的成员nalu->buf
	NALU_t *nalu;//雷神自定义的NALU数据,额外包含头部与长度信息
	nalu = (NALU_t*)calloc(1, sizeof(NALU_t));
	if (nalu == NULL) {
		printf("Alloc NALU Error\n");
		return -1;
	}
	int buffersize = 100000;//临时缓存,足够大于一个NALU的字节数即可
	nalu->max_size = buffersize;
	nalu->buf = (char*)calloc(buffersize, sizeof(char));
	if (nalu->buf == NULL) {
		free(nalu);
		printf("AllocNALU: n->buf");
		return -1;
	}

	//累加每一次的偏移量,用于记录每个NALU的起始地址,雷神的这个偏移量是包括对应的起始码(3或者4字节),即显示的POS字段
	int data_offset = 0;
	int nal_num = 0;//NALU的数量,从0开始算
	int data_lenth;//接收返回值,文件指针的位置,也就是下一起始码首字节,或者说下一NALU的偏移地址
	printf("-----+-------- NALU Table ------+---------+\n");
	printf(" NUM |    POS  |    IDC |  TYPE |   LEN   |\n");
	printf("-----+---------+--------+-------+---------+\n");

	//3 循环读取码流获取NALU
	while (!feof(h264bitstream))
	{
		data_lenth = GetAnnexbNALU(nalu);

		//4 获取NALU的类型
		char type_str[20] = { 0 };
		switch (nalu->nal_unit_type) {
		case NALU_TYPE_SLICE:sprintf(type_str, "SLICE"); break;
		case NALU_TYPE_DPA:sprintf(type_str, "DPA"); break;
		case NALU_TYPE_DPB:sprintf(type_str, "DPB"); break;
		case NALU_TYPE_DPC:sprintf(type_str, "DPC"); break;
		case NALU_TYPE_IDR:sprintf(type_str, "IDR"); break;
		case NALU_TYPE_SEI:sprintf(type_str, "SEI"); break;
		case NALU_TYPE_SPS:sprintf(type_str, "SPS"); break;
		case NALU_TYPE_PPS:sprintf(type_str, "PPS"); break;
		case NALU_TYPE_AUD:sprintf(type_str, "AUD"); break;
		case NALU_TYPE_EOSEQ:sprintf(type_str, "EOSEQ"); break;
		case NALU_TYPE_EOSTREAM:sprintf(type_str, "EOSTREAM"); break;
		case NALU_TYPE_FILL:sprintf(type_str, "FILL"); break;
		}
		//5 获取NALU的IDC即优先级
		char idc_str[20] = { 0 };
		switch (nalu->nal_reference_idc >> 5) {
		case NALU_PRIORITY_DISPOSABLE:sprintf(idc_str, "DISPOS"); break;
		case NALU_PRIRITY_LOW:sprintf(idc_str, "LOW"); break;
		case NALU_PRIORITY_HIGH:sprintf(idc_str, "HIGH"); break;
		case NALU_PRIORITY_HIGHEST:sprintf(idc_str, "HIGHEST"); break;
		}

		//6 输出nal个数 此时数据的偏移量,优先级,NALU类型,NALU数据的长度
		fprintf(myout, "%5d| %8d| %7s| %6s| %8d|\n", nal_num, data_offset, idc_str, type_str, nalu->len);

		//7 记录下一NALU的偏移地址,即计算后,该偏移地址就是下一NALU的偏移地址.例如显示0后,0+29就是下一NALU的偏移地址
		data_offset = data_offset + data_lenth;
		nal_num++;
	}

	//8 Free掉nalu与nalu->buf
	if (nalu != NULL) {
		if (nalu->buf != NULL) {
			free(nalu->buf);
			nalu->buf = NULL;
		}
		free(nalu);
		nalu = NULL;
	}
	return 0;
}

int main() {

	char fileName[100] = "sintel.h264";
	simplest_h264_parser(fileName);

	return 0;
}

结果分析:

我的:
在这里插入图片描述

雷神的:
在这里插入图片描述
在这里插入图片描述
结果可以看出,雷神的是由下标0开始算第一个SPS的NALU,而我是从1开始算第一个NALU的,代码可以从nalu.size()!=0中看出,即第一次的大小为0,导致第一个NALU数据对应的cnt=1。所以都是667个NALU数据,证明正确
上面雷神唯一我没看懂的一点就是:文件最后一个NALU没找到起始码时,pos为什么要减1操作,pos不是指向末尾了吗(end即eof),直接减去起始码长度不就可以了吗?有空再测测。。。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FFMPEG之H264获取NALU并且解析其RBSP类型03 的相关文章

  • 如何在Android项目中使用libffmpeg.so?

    我正在尝试在 Android 中创建一个屏幕录制应用程序 为此 我使用 FFmpeg 我已经创建了 libffmpeg so 文件 现在我想在 Android 项目中使用相同的方法来调用它的本机函数 我怎样才能做到这一点 本教程提供了有关此
  • 采用 std::vector 或 std::array 的模板函数

    我有一个函数 当前接受 2 个向量 其中可以包含任何普通的旧数据 template
  • 在 WCF 上重用我的 PagedList 对象

    问题 我有一个自定义集合PagedList
  • 如何通过覆盖 MSBuild 目标来防止外语资源生成?

    我正在致力于减少大型 C ASP NET 解决方案的编译时间 我们的解决方案使用通常的 resx 文件方法翻译成大约十几种外语 这些资源文件的解析和编译极大地减慢了我们的编译时间 并且是日常的挫败感 我知道可以创建自定义资源提供程序并摆脱
  • 如何通过 libwebsocket 发送异步数据?

    我正在将 Warmcat 的 libwebsocket C 库用于小型 Websocket 服务器 我已经启动并运行了这些示例 并且可以发送数据以响应从 websocket 接收数据 例如回显发送的反向字节 但是 我无法弄清楚如何在不使用
  • 如何从不同的线程访问控件?

    如何从创建控件的线程以外的线程访问控件 避免跨线程错误 这是我的示例代码 private void Form1 Load object sender EventArgs e Thread t new Thread foo t Start p
  • C# 中的抽象类和接口类有什么不同?

    C 中的抽象类和接口类有什么不同 An 接口不是类 它只是一个contract定义了public一个类的成员must实施 抽象类只是一个类 您从中可以cannot创建一个实例 通常您会使用它来定义一个基类 该基类定义了一些virtual方法
  • 查找方法不适用于 EF6.1 模拟

    我已经使用这些 msdn 指南设置了模拟 使用模拟框架进行测试 EF6 及以上 http msdn microsoft com en us data dn314429 var bsAc db BusAcnts FirstOrDefault
  • ASP.net WebForms - 在标记中使用 GetRouteUrl

    我一直在尝试弄清楚如何将路由功能与 ASP net 4 0 WebForms 一起使用 我将一条路线添加到我的路线集合中 void Application Start RegisterRoutes RouteTable Routes voi
  • Windows 上本机 C++ 应用程序中的自动死代码检测?

    背景 我有一个用原生 C 编写的应用程序 花了几年的时间 大约有 60 KLOC 有很多函数和类已经死了 可能有 10 15 就像下面提出的类似的基于 Unix 的问题 我们最近开始对所有新代码进行单元测试 并尽可能将其应用于修改后的代码
  • 使用 cmake 将两种解决方案合二为一

    我有两个单独的 Visual Studio 2013 解决方案 我想将它们迁移到一个解决方案中 因为第一个解决方案 使用 Qt 充当第二个解决方案的 GUI 最后 我希望有一个结构如下的单一解决方案 Solution All Build P
  • 应在堆栈上分配的最大数量

    我一直在寻找堆栈溢出有关应在堆栈上分配的最大内存量的指南 我看到了堆栈与堆分配的最佳实践 但没有关于应该在堆栈上分配多少以及应该在堆上分配多少的指南 有什么想法 数字可以作为指导吗 什么时候应该在堆栈上分配 什么时候应该在堆上分配 多少才算
  • 从具有相同属性的另一个对象创建对象

    我有一个 C 对象 可以说有 20 个属性 它是数据契约的一部分 我还有另一个具有类似属性的业务实体 我想从响应对象中填充该实体 除了将一个对象的每个属性分配给另一个对象的相应属性之外 还有其他方法可以做到这一点吗 是的 看看自动映射器 h
  • 意外的 const 引用行为

    include
  • 如果仅使用第一个元素,是否必须为整个结构分配内存?

    我有一个结构 其中第一个元素被测试 并且根据其值 结构的其余部分将被读取或不会被读取 在第一个元素的值指示结构的其余部分不会被读取的情况下 我是否必须为整个结构或仅第一个元素分配足够的内存 struct element int x int
  • C - 获取外部IP地址

    我需要通过 C C 调用获取我的公共 IP 地址 我知道作为替代方案 我可以从 http whatismyip akamai com 等外部链接获取 我写了一个示例来获取外部IP地址 但我的程序没有返回外部 IP 地址 我正在获取内部 IP
  • 使用C标准数学库精确计算标准正态分布的CDF

    标准 C 数学库不提供计算标准正态分布 CDF 的函数 normcdf 然而 它确实提供了密切相关的函数 误差函数 erf 和互补误差函数 erfc 计算 CDF 的最快方法通常是通过误差函数 使用预定义常量 M SQRT1 2 来表示 d
  • C# PasswordDeriveBytes:似乎 Salt 并不重要

    可能我误解了什么 以下代码通过 CryptDeriveKey 使用两种不同的盐生成两个相等的密钥 这是控制台结果 盐1 21 3e 18 a3 9a 8b 5f gt 键 da 89 ea 3d 91 08 20 98 20 e9 dc 4
  • 检查一个数是否是完全平方数?

    我认为以下代码存在精度问题 bool isPerfectSquare long long n long long squareRootN long long sqrt n 0 5 return squareRootN squareRootN
  • 创建进程默认浏览器

    我目前正在使用 ShellExecute 打开 在用户浏览器中打开 URL 但在 Win7 和 Vista 中遇到了一些麻烦 因为该程序作为服务运行提升 我想获取线程 id 因此 ShellExecute 无法获取线程 id 因此我开始使用

随机推荐

  • Pycharm上Modify Run Configuration的使用方法,带参数配置

    前言 我们在搭建yolo系列目标检测模型时 往往需要对代码进行逐步调试 及时发现错误 所以本文在pycharm的基础上 对yolov6中的infer py进行逐步调试 首先我们在conda环境一切准备就绪的情况下 能在终端tenminal中
  • 【UE5 Cesium】11-Cesium for Unreal 切换Dynamic Pawn为其它Pawn

    前言 我们知道在Cesium for Unreal中默认使用的是DynamicPawn来浏览地图场景 DynamicPawn适用全球浏览 可以按自定义曲线进行飞行 但是DynamicPawn是使用的是地理参考坐标系 并不是标准的UE坐标系
  • iOS利用九切片进行切图UI不会变形

    p 1 手写代码 p p p UIImageView svRect UIImage backgroundImage UIImageimageNamed bg png backgroundImage backgroundImageresiza
  • 《机器学习》读书笔记2--线性模型

    目录 线性模型基本形式 线性回归 对数几率回归 线性判别分析 多分类学习 类别不平衡问题 ps 写在前面 本文是在参加datawhale组队学习 学习周志华老师的 机器学习 过程的学习笔记 文中出现的图片均引自 机器学习 机器学习 是初学者
  • AI绘画Stable Diffusion原理之扩散模型DDPM

    前言 传送门 stable diffusion Git 论文 stable diffusion webui Git Google Colab Notebook部署stable diffusion webui Git kaggle Noteb
  • 量化交易框架开发实践(一)

    量化交易平台指支持通过对数据进行多维度的定量分析 结合发现的特征定制策略 并能够基于历史数据对策略进行回测 最后支持实盘买卖的交易平台 从业务流上看 量化交易可以分解成 行情获取 gt 数据清洗 gt 指标计算 gt 策略开发 gt 策略回
  • RobotStudio ABB 仿真软件过期 后的处理

    首先查看当前是在试用期还是已经过期了 查看方法如下 在打开软件后的首页找到 帮助 右侧会显示当前授权状态是否为 试用 1 如果当前在试用期内 可以通过直接修改注册表方式 修改方法 找到如下位置 HKEY LOCAL MACHINE SOFT
  • 正则表达式匹配中* . c++实现

    题目描述 请实现一个函数用来匹配包括 和 的正则表达式 模式中的字符 表示任意一个字符 而 表示它前面的字符可以出现任意次 包含0次 在本题中 匹配是指字符串的所有字符匹配整个模式 例如 字符串 aaa 与模式 a a 和 ab ac a
  • QT 怎么导入qss文件?

    方式一 比较常见的方法 QFile file qss psblack css if file open QFile ReadOnly QString qss QLatin1String file readAll qApp gt setSty
  • 提高电脑寿命的维护技巧与方法分享

    在维护电脑运行方面 我有一些自己觉得非常有用的技巧和方法 下面我将分享一些我常用的维护技巧 并解释为什么我会选择这样做以及这样做的好处 首先 我经常清理我的电脑内部的灰尘 电脑内部的灰尘会影响散热效果 导致电脑发热严重甚至性能下降 因此 定
  • Google Colab 上部署 Stable Diffusion Web UI

    什么是 Stable Diffusion Web UI Colab Stable Diffusion 是 Stability AI 推出的一个基于深度学习技术文字生成图片AI模型 Stable Diffusion Web UI 是一个强大好
  • [技术经理]03 到底是能力重要,还是态度重要?

    对于一个技术团队而言 团队里面的人员是最最重要的财富 人员的招聘和人员的管理是技术经理最重要的工作之一 但是 事实也是 没有什么问题比人的问题更难处理的了 我先讲两个发生在我们团队里面的事件 今年上半年的时候 我们团队同时入职了两名前端开发
  • C语言中sizeof()和strlen()的区别

    sizeof 一 sizeof的基本概念 sizeof操作符以字节形式给出了其操作数的存储大小 操作数可以是一个表达式或括在括号 内的类型名 操作数的存储大小由操作数的类型决定 二 使用方法 1 用于数据类型 sizeof使用形式 size
  • UBUNTU16.04命令行安装PCL1.7(亲测有效)

    安装PCL点云库 最开始是按照先安装相关依赖 然后github上clone PCL相关版本包的形式安装的 编译遇到了很多问题 结果最后安装完成 但却无法运行例程 难过 又尝试了命令行的形式 命令行的形式直观简单 可安装编译好的点云库 PCL
  • [Python人工智能] 九.gensim词向量Word2Vec安装及《庆余年》中文短文本相似度计算

    从本专栏开始 作者正式开始研究Python深度学习 神经网络及人工智能相关知识 前一篇详细讲解了卷积神经网络CNN原理 并通过TensorFlow编写CNN实现了MNIST分类学习案例 本篇文章将分享gensim词向量Word2Vec安装
  • 时频分析常用工具:STFT短时傅里叶变换 & 小波变化

    文章目录 1 傅里叶变换的局限性 2 STFT 3 小波变换 参考 时频分析之STFT 短时傅里叶变换的原理与实现 形象易懂讲解算法I 小波变换 https www zhihu com question 58814934 1 傅里叶变换的局
  • MSCOCO数据集格式转化成VOC数据集格式

    MSCOCO数据集格式转化成VOC数据集格式 转载请注明原出处 http blog csdn net ouyangfushu article details 79543575 作者 SyGoing QQ 2446799425 SSD目标检测
  • CUDA(C)和PyCUDA(Python) GPU加速OpenCV视觉

    CUDA 本节介绍一个简单的加法程序 该程序在设备上执行两个变量的加法 虽然它没有利用设备的任何数据并行性 但它对于演示 CUDA C 的重要编程概念非常有用 首先 我们将看到如何编写一个用于添加两个变量的内核函数 内核函数的代码如下所示
  • 面试之JVM类的生命周期

    按照Java虚拟机规范 从class文件到加载到内存中的类 到类卸载出内存为止 它的整个生命周期包括如下7个阶段 加载 类的加载指的是将类的 class文件中的二进制数据读取到内存中 存放在运行时数据区的方法去中 在加载的过程中 jvm需要
  • FFMPEG之H264获取NALU并且解析其RBSP类型03

    FFMPEG之H264获取NALU并且解析其RBSP类型03 前言 FFMPEG之H264理论篇 理论的就不多讲了 可以参考上面那篇文章 下面将给出两种版本 一种是我自己的 用C 方法实现 另一种是雷神版本的 基本是纯C语言 区别是我多了一