【视频/图像数据格式】基本视频/图像数据格式

基本视频/图像数据格式

  • 1.概述
  • 2.视频图像数据格式
    • 2.1 yuv420p
    • 2.2 yuv422p
    • 2.3 yuv444p
    • 2.4 RGB格式
    • 2.5 BMP格式
  • 3.格式转换
    • 3.1 RGB24转换为YUV420P
  • 4.视频图像评价指标
    • 4.1 MSE
    • 4.2 PSNR

参考:

  1. 雷霄骅博士博客:
  • http://t.csdnimg.cn/kl2jL
  • http://t.csdnimg.cn/pMLLE
  • http://t.csdnimg.cn/FjtOK
  • http://t.csdnimg.cn/K95yN
  1. ffmpeg-7.0中/test/utils.c文件

1.概述

视频/图像数据格式作为视频/图像处理的对象,是整个视频图像处理体系的最基本单元,有必要熟悉其存储格式。主要视频图像数据格式有yuv420p,yuv422p,yuv444p,bmp和rgb24等,其中视频编码器的主要输入数据格式就是yuv420p,因为这种格式存储数据量很小,易于存储。

视频图像处理体系大致可以分为几层:

  1. 协议层(http、rtmp、file…)
  2. 封装层(mkv,mp4,flv,mpegts,avi…)
  3. 编解码层(h264,h265,mpeg2…)
  4. 像素层(yuv420p,yuv422p,yuv444p,rgb24…)

这里仅关注像素层(视频图像的主要组成部分)的数据格式。

2.视频图像数据格式

2.1 yuv420p

最常见的视频编码数据格式,yuv420p格式中,p的含义是planar,即平面存储。存储时分为三个分量Y、U和V,即在内存当中存储时先存储Y,后存储U,最后V。如果是420p格式,U和V分量的数据大小均为Y分量的1/4,这是因为U和V分量的width和height均为Y分量的1/2。
在内存中,YUV420P的存储方式为:Y0 Y1 Y2 … U0 U1 U2 … V0 V1 V2 …,并且Y的长度分别为U和V分量的4倍。如果不是p格式,可能存储的方式是interleave,即交叉式存储,这里不讨论。YUV420p格式的数据,其读取和写入数据的方式为

int video_yuv420_split(char *url, int w, int h, int num){
	FILE *fp = fopen(url, "rb+");
	FILE *fp_y = fopen("output_420_y.y", "wb+");
	FILE *fp_u = fopen("output_420_u.y", "wb+");
	FILE *fp_v = fopen("output_420_v.y", "wb+");

	// 3/2 = 1(Y) + 1/4(U) + 1/4(V)
	unsigned char *pic = (unsigned char *) mallo c(w * h * 3/2);
	for(int i = 0; i < num; i++){
		fread(pic, 1, w * h * 3/2, fp); 				// Read YUV data from .yuv file
		fwrite(pic, 1, w * h, fp_y);					// Write Y component into fp_y
		fwrite(pic + w * h, 1, w * h / 4, fp_u);		// Write U component into fp_u
		fwrite(pic + w * h * 5 / 4, 1, w * h / 4, fp_v);// Write V component into fp_v
	}

	free(pic);
	fclose(fp);
	fclose(fp_y);
	fclose(fp_u);
	fclose(fp_v);
	return 0;
}

2.2 yuv422p

与yuv420p类似,区别在于U和V分量的比例不同。yuv422p格式当中,仍然是先存储Y分量,后存储U分量,最后是V分量。但是U和V分量的大小分别是Y分量的一半,例如 Y0 Y1 Y2 Y3 U0 U1 V0 V1。读写方式和yuv420p的区别在于

	unsigned char *pic = (unsigned char *) malloc (w * h * 2);
	for(int i = 0; i < num; i++){
		fread(pic, 1, w * h * 2, fp); 					// Read YUV data from .yuv file
		fwrite(pic, 1, w * h, fp_y);					// Write Y component into fp_y
		fwrite(pic + w * h, 1, w * h / 2, fp_u);		// Write U component into fp_u
		fwrite(pic + w * h * 3 / 2, 1, w * h / 2, fp_v);// Write V component into fp_v
	}

2.3 yuv444p

yuv444p格式中,YUV三个分量的大小相同,例如 Y0 Y1 Y2 Y3 U0 U1 U2 U3 V0 V1 V2 V3。读写方式为

	unsigned char *pic = (unsigned char *) malloc (w * h * 3);
	for(int i = 0; i < num; i++){
		fread(pic, 1, w * h * 3, fp); 				// Read YUV data from .yuv file
		fwrite(pic, 1, w * h, fp_y);				// Write Y component into fp_y
		fwrite(pic + w * h, 1, w * h, fp_u);		// Write U component into fp_u
		fwrite(pic + w * h * 2, 1, w * h, fp_v);	// Write V component into fp_v
	}

2.4 RGB格式

对于后缀为.rgb格式的文件,其存储数据的方式与yuv不同。在YUV格式当中,YUV三个通道是分别进行存储,而RGB格式是三个通道交替进行存储,例如 r0 g0 b0 r1 g1 b1 r2 g2 b2…,因此其读写数据的方式也不同。

	unsigned char *pic = (unsigned char *) malloc (w * h * 3);
	for(int i = 0; i < num; i++){
		fread(pic, 1, w * h * 3, fp);			// read .rgb file
		for(int j = 0; j < w * h * 3; j = j + 3){
			fwrite(pic + j, 1, 1, fp_y);		// write r component
			fwrite(pic + j + 1, 1, 1, fp_u);	// write g component
			fwrite(pic + j + 2 , 1, 1, fp_v);	// write b component
		}

常见的8种颜色RGB数值为

颜色RGB
(255, 255, 255)
(255, 255, 0)
(0, 255, 255)
绿( 0, 255, 0)
品红(255, 0, 255)
(255, 0, 0)
(0, 0, 255)
(0, 0, 0)

另外,灰色为128

2.5 BMP格式

BMP格式是对RGB进行封装得到的格式,能够使用普通的图片浏览器打开。对RGB格式进行封装得到BMP格式的方式如下:

/**
 1. Convert RGB24 file to BMP file
 2. @param rgb24path    Location of input RGB file.
 3. @param width        Width of input RGB file.
 4. @param height       Height of input RGB file.
 5. @param url_out      Location of Output BMP file.
 */
int video_rgb24_to_bmp(const char *rgb24path,int width,int height,const char *bmppath){
	typedef struct 
	{  
		long imageSize;
		long blank;
		long startPosition;
	}BmpHead;
 
	typedef struct
	{
		long  Length;
		long  width;
		long  height;
		unsigned short  colorPlane;
		unsigned short  bitColor;
		long  zipFormat;
		long  realSize;
		long  xPels;
		long  yPels;
		long  colorUse;
		long  colorImportant;
	}InfoHead;
 
	int i = 0;
	int j = 0;
	BmpHead m_BMPHeader = { 0 };
	InfoHead  m_BMPInfoHeader = { 0 };
	char bfType[2] = {'B', 'M'};
	int header_size = sizeof(bfType) + sizeof(BmpHead) + sizeof(InfoHead);
	unsigned char *rgb24_buffer = NULL;
	FILE* fp_rgb24 = NULL;
	FILE* fp_bmp = NULL;
 
	if((fp_rgb24 = fopen(rgb24path, "rb")) == NULL){
		printf("Error: Cannot open input RGB24 file.\n");
		return -1;
	}
	if((fp_bmp = fopen(bmppath, "wb")) == NULL){
		printf("Error: Cannot open output BMP file.\n");
		return -1;
	}
 
	rgb24_buffer = (unsigned char *)malloc(width * height * 3);
	fread(rgb24_buffer, 1, width * height * 3, fp_rgb24);
 
	m_BMPHeader.imageSize = 3 * width * height + header_size;
	m_BMPHeader.startPosition = header_size;
 
	m_BMPInfoHeader.Length = sizeof(InfoHead); 
	m_BMPInfoHeader.width = width;
	//BMP storage pixel data in opposite direction of Y-axis (from bottom to top).
	m_BMPInfoHeader.height =- height;
	m_BMPInfoHeader.colorPlane = 1;
	m_BMPInfoHeader.bitColor = 24;
	m_BMPInfoHeader.realSize = 3 * width * height;
 
	fwrite(bfType, 1, sizeof(bfType), fp_bmp);
	fwrite(&m_BMPHeader, 1, sizeof(m_BMPHeader), fp_bmp);
	fwrite(&m_BMPInfoHeader, 1, sizeof(m_BMPInfoHeader), fp_bmp);
 
	//BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2
	//It saves pixel data in Little Endian
	//So we change 'R' and 'B'
	for(j = 0; j < height; j++){
		for(i = 0; i < width; i++){
			// 将R分量和B分量的位置进行交换
			char temp = rgb24_buffer[(j * width + i) * 3 + 2];
			rgb24_buffer[(j * width + i) * 3 + 2] = rgb24_buffer[(j * width + i) * 3 + 0];
			rgb24_buffer[(j * width + i) * 3 + 0] = temp;
		}
	}
	fwrite(rgb24_buffer, 3 * width * height, 1, fp_bmp);
	fclose(fp_rgb24);
	fclose(fp_bmp);
	free(rgb24_buffer);
	printf("Finish generate %s!\n", bmppath);
	return 0;
}

在这里,代码执行的任务包括:

  1. 存储写上BMP的头部信息
  2. 将RGB格式的文件修改为BGR。这是因为BMP存储时使用的是小端存储(Little Endian),存储时的顺序为B、G、R

BMP文件是由BITMAPFILEHEADER、BITMAPINFOHEADER、RGB像素数据共3个部分构成,如下所示。其中,BITMAPFILEHEADER对应上述的BmpHead,BITMAPINFOHEADER对应上述的InfoHead。

typedef  struct  tagBITMAPFILEHEADER
{ 
	unsigned short int  bfType;       //位图文件的类型,必须为BM 
	unsigned long       bfSize;       //文件大小,以字节为单位
	unsigned short int  bfReserverd1; //位图文件保留字,必须为0 
	unsigned short int  bfReserverd2; //位图文件保留字,必须为0 
	unsigned long       bfbfOffBits;  //位图文件头到数据的偏移量,以字节为单位
}BITMAPFILEHEADER; 
typedef  struct  tagBITMAPINFOHEADER 
{ 
	long biSize;                    //该结构大小,字节为单位
	long  biWidth;                  //图形宽度以象素为单位
	long  biHeight;                 //图形高度以象素为单位
	short int  biPlanes;            //目标设备的级别,必须为1 
	short int  biBitcount;          //颜色深度,每个象素所需要的位数
	short int  biCompression;       //位图的压缩类型
	long  biSizeImage;              //位图的大小,以字节为单位
	long  biXPelsPermeter;       	//位图水平分辨率,每米像素数
	long  biYPelsPermeter;       	//位图垂直分辨率,每米像素数
	long  biClrUsed;            	//位图实际使用的颜色表中的颜色数
	long  biClrImportant;       	//位图显示过程中重要的颜色数
}BITMAPINFOHEADER;

3.格式转换

3.1 RGB24转换为YUV420P

RGB24转换YUV420p的公式为:

Y = 0.299 * R + 0.587 * G + 0.114 * B
U =-0.147 * R - 0.289 * G + 0.463 * B
V = 0.615 * R - 0.515 * G - 0.100 * B

代码参考ffmpeg-7.0当中的/test/utils.c,这个文档相比雷霄骅博士的写法有所不同,或许更好理解。

#define SCALEBITS 8
#define ONE_HALF  (1 << (SCALEBITS - 1))
#define FIX(x)    ((int) ((x) * (1 << SCALEBITS) + 0.5))	// 乘以255倍,猜测目的应该是提升精度
#define err_if(expr) do {                                              \
    if (expr) {                                                        \
        fprintf(stderr, "%s\n", strerror(errno));                      \
        exit(1);                                                       \
    }                                                                  \
} while (0)

static void rgb24_to_yuv420p(unsigned char *lum, unsigned char *cb, // lum是Y分量的地址,cb是U分量的地址
                             unsigned char *cr, const unsigned char *src,	// cr是V分量的地址
                             int width, int height)
{
    int wrap, wrap3, x, y;
    int r, g, b, r1, g1, b1;
    const unsigned char *p;

    wrap  = width;		// yuv指针偏移量,用于定位图像每一行的宽度
    wrap3 = width * 3;	// rgb指针偏移量,用于定位图像每一行的宽度
    p     = src;		// src为rgb图像的指针地址
    // 这里每2x2个像素进行处理,是因为U和V分量的长和宽分别只占据Y分量的1/2
    for (y = 0; y < height; y += 2) { 
        for (x = 0; x < width; x += 2) {
            r       = p[0];
            g       = p[1];
            b       = p[2];
            r1      = r;
            g1      = g;
            b1      = b;
            lum[0]  = (FIX(0.29900) * r + FIX(0.58700) * g +
                       FIX(0.11400) * b + ONE_HALF) >> SCALEBITS;
            r       = p[3];
            g       = p[4];
            b       = p[5];
            r1     += r;
            g1     += g;
            b1     += b;
            lum[1]  = (FIX(0.29900) * r + FIX(0.58700) * g +
                       FIX(0.11400) * b + ONE_HALF) >> SCALEBITS;
            p      += wrap3;	// 移动到当前2x2小块的左下小块
            lum    += wrap;		// 移动到当前2x2小块的左下小块

            r       = p[0];
            g       = p[1];
            b       = p[2];
            r1     += r;
            g1     += g;
            b1     += b;
            lum[0]  = (FIX(0.29900) * r + FIX(0.58700) * g +
                       FIX(0.11400) * b + ONE_HALF) >> SCALEBITS;
            r       = p[3];
            g       = p[4];
            b       = p[5];
            r1     += r;
            g1     += g;
            b1     += b;
            lum[1]  = (FIX(0.29900) * r + FIX(0.58700) * g +
                       FIX(0.11400) * b + ONE_HALF) >> SCALEBITS;
			// 每2x2个像素有一个Cb和Cr分量,将其写入到cb和cr数组当中
            cb[0]   = ((- FIX(0.16874) * r1 - FIX(0.33126) * g1 +
                       FIX(0.50000) * b1 + 4 * ONE_HALF - 1) >> (SCALEBITS + 2)) + 128;
            cr[0]   = ((FIX(0.50000) * r1 - FIX(0.41869) * g1 -
                       FIX(0.08131) * b1 + 4 * ONE_HALF - 1) >> (SCALEBITS + 2)) + 128;

            cb++;
            cr++;
            p   += -wrap3 + 2 * 3;	// 乘以3是因为rgb是顺序存储的,移动到下一个2x2小块左上小块的r分量
            lum += -wrap  + 2;		// 回到上一行的起始位置,加2则指向下一个2x2小块的左上小块
        }
        p   += wrap3;
        lum += wrap;
    }
}


static void pgmyuv_save(const char *filename, int w, int h,
                        const unsigned char *rgb_tab)
{
    FILE *f;
    int i, h2, w2;
    unsigned char *cb, *cr;
    unsigned char *lum_tab, *cb_tab, *cr_tab;

    lum_tab = malloc(w * h);
    cb_tab  = malloc(w * h / 4);
    cr_tab  = malloc(w * h / 4);

    rgb24_to_yuv420p(lum_tab, cb_tab, cr_tab, rgb_tab, w, h);

    if (filename) {
        f = fopen(filename, "wb");
        fprintf(f, "P5\n%d %d\n%d\n", w, h * 3 / 2, 255);
    } else {
        f = stdout;
    }

    err_if(fwrite(lum_tab, 1, w * h, f) != w * h); // 写入Y分量
    h2 = h / 2;
    w2 = w / 2;
    cb = cb_tab;
    cr = cr_tab;

    if (filename) {
        for (i = 0; i < h2; i++) {
            err_if(fwrite(cb, 1, w2, f) != w2);	// 写入U分量
            err_if(fwrite(cr, 1, w2, f) != w2);	// 写入V分量
            cb += w2;
            cr += w2;
        }
        fclose(f);
    } else {
        for (i = 0; i < h2; i++) {
            err_if(fwrite(cb, 1, w2, f) != w2);
            cb += w2;
        }
        for (i = 0; i < h2; i++) {
            err_if(fwrite(cr, 1, w2, f) != w2);
            cr += w2;
        }
    }

    free(lum_tab);
    free(cb_tab);
    free(cr_tab);
}

4.视频图像评价指标

4.1 MSE

MSE全称为Mean Square Error,表示均方误差,其计算方式为

int width = WIDTH;
int height = HEIGHT;
double mse = 0.0;
for(int j = 0; j < width * height; j++){
	mse += pow((double)(src[j] - dst[j]), 2);
}
mse = mse / (width * height);

4.2 PSNR

PSNR的计算是在MSE计算的基础之上获得的,计算方式为

double psnr = 0.0;
psnr = 10 * log10(255.0 * 255.0 / mse);

PSNR描述了两幅图片的差异程度,单位是dB,dB越大,表示两幅图像越接近,否则差异越大。在视频编码标准中,PSNR是衡量编码工具的重要指标,通常与Bitrate结合起来,来评判编码算法的优劣。

CSDN:https://blog.csdn.net/weixin_42877471
Github:https://github.com/DoFulangChen

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的“环卫工管理平台”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“环卫工管理平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体设计图 注册 首页 后台登录 后台页面 环卫工管理 摘…

mac安装 php7.1/apache

1. 安装php 7.1 brew tap shivammathur/php 查看可安装版本 brew search php 安装指定版本&#xff08;禅道适用PHP运行环境(7.0/7.1/7.2版本)&#xff09; brew install php7.1 环境配置 vim ~/.zshrc export PATH"/usr/local/opt/php7.1/bin:$PATH" export …

使用Postman进行接口测试---解析postman页面

一、Postman 是一款流行的 API 测试工具&#xff0c;它提供了丰富的功能来帮助开发者测试和调试 API。以下是 Postman 页面上的主要功能及其含义和作用&#xff1a; 1. 请求详情&#xff08;Request Details&#xff09; &#xff1a; - 方法&#xff08;Method&#xff0…

【读论文】Gaussian Grouping: Segment and Edit Anything in 3D Scenes

Gaussian Grouping: Segment and Edit Anything in 3D Scenes 文章目录 Gaussian Grouping: Segment and Edit Anything in 3D Scenes1. What2. Why3. How3.1 Anything Mask Input and Consistency3.2 3D Gaussian Rendering and Grouping3.3 Downstream: Local Gaussian Editi…

第二篇【AI与传奇开心果系列】Python的AI技术点库案例示例:详解AI工业应用算法原理

AI与传奇开心果系列博文 系列博文目录Python的AI技术点库案例示例系列 博文目录前言一、AI工业应用算法原理介绍二、机器学习在工业领域的应用算法示例代码三、深度学习算法在工业领域应用示例代码四、强化学习在工业领域应用示例代码五、自然语言处理在工业领域应用示例代码六…

C语言 变量的作用域

今天 我们来说变量的作用域和存储类型 每种事物 都有自己作用的范围限制 例如 汽车只能在路上跑 轮船只能在海洋 飞机只能通行于天空 函数的参数 也只有在函数被调用过程中分配内存资源 函数执行结束 空间也会被立即释放 这也说明了 行参变量只有在函数内才有效 离开了该函数 …

【JavaEE】博客系统(前端页面设计)

文章目录 一、预期效果二、实现博客列表页 一、预期效果 二、实现博客列表页 实现导航栏 编辑 blog_list.html, 创建导航栏的 html 代码. 导航栏里面包含 logo, 标题, 以及一些按钮(跳转链接). 为了实现左右排列, 在 logo 和 按钮 之间加一个 spacer 作为占位器. <!-- 导航…

初次查询大数据信用报告,需要注意哪些问题?

随着大数据的普及&#xff0c;基于大数据技术的大数据信用也变得越来越重要&#xff0c;比如在申贷之前&#xff0c;不少地方都会查询申贷人的大数据信用&#xff0c;作为风险控制的必要手段&#xff0c;那对于初次查询大数据信用报告的人来说&#xff0c;需要注意哪些问题呢?…

引领AI数据标注新纪元:景联文科技为智能未来筑基

在人工智能蓬勃发展的今天&#xff0c;数据如同燃料&#xff0c;驱动着每一次技术飞跃。在这场智能革命的浪潮中&#xff0c;景联文科技凭借其深厚的专业实力与前瞻性的战略眼光&#xff0c;正站在行业前沿&#xff0c;为全球的人工智能企业提供坚实的数据支撑。 全国布局&…

vscode的git插件使用教程

虽然git的命令我没有滚瓜烂熟&#xff0c;但vscode的git插件是尊嘟很好用啊&#xff0c;都被我用烂了。在网上看见一个讲的很不错的插件教程。借鉴一下。并在一些地方用块引用进行了补充说明&#xff01; 跳过了vscode安装过程。 克隆GitHub中的存储库&#xff1a; 1、复制Gi…

python3有serial库吗

一、概述 pyserial模块封装了对串口的访问。 二、特性 在支持的平台上有统一的接口。 通过python属性访问串口设置。 支持不同的字节大小、停止位、校验位和流控设置。 可以有或者没有接收超时。 类似文件的API&#xff0c;例如read和write&#xff0c;也支持readline等…

探案录 | KingbaseES+SqlSugar为医疗用户排忧解难

在2024年的初春&#xff0c;某大型三甲医院的CT预约系统上线测试&#xff0c;如同新芽破土&#xff0c;充满了希望与活力。然而&#xff0c;仅仅两天后&#xff0c;一个技术难题如同迷雾中的幽灵&#xff0c;悄然出现&#xff1a;The connection pool has been exhausted…… 福…

5.07 Pneumonia Detection in Chest X-Rays using Neural Networks

肺炎诊断是一个耗时的过程&#xff0c;需要高技能的专业人员分析胸部X光片chest X-ray (CXR)&#xff0c;并通过临床病史、生命体征和实验室检查确认诊断。 它可以帮助医生确定肺部感染的程度和位置。呼吸道疾病在 X 光片上表现为一处膨胀的不透明区域。然而&#xff0c;由于不…

STM32 ADC学习

ADC Analog-to-Digital Converter&#xff0c;即模拟/数字转换器 常见ADC类型 分辨率和采样速度相互矛盾&#xff0c;分辨率越高&#xff0c;采样速率越低。 ADC的特性参数 分辨率&#xff1a;表示ADC能辨别的最小模拟量&#xff0c;用二进制位数表示&#xff0c;比如8,10…

OpenAI 正在开发一种可以防止版权诉讼的工具

OpenAI 正在开发一种名为 Media Manager 的工具&#xff0c;该工具将使内容创建者和所有者能够确定他们是否愿意将自己的内容用于 ML 研究和 AI 模型训练。 Media Manager 将做什么&#xff1f; 明年推出的 Media Manager 将使内容创作者和所有者能够更好地控制他们的工作是否…

C语言初阶(6) - 指针

目录 1.指针是什么&#xff1f; 2. 指针和指针类型 2.1 指针 - 整数 2.2 指针的解引用 3. 野指针 3.1 野指针成因 3.2 如何规避野指针 4. 常量指针和指针常量 (const) 4.1.常量指针 4.2.指针常量 5. 指针运算 5.1 指针-整数 5.2 指针-指针 5.3指针的关系运算 6.…

Vitis HLS 学习笔记--理解串流Stream(2)

目录 1. 简介 2. 极简的对比 3. 硬件模块的多次触发 4. 进一步探讨 do-while 5. 总结 1. 简介 在这篇博文中《Vitis HLS 学习笔记--AXI_STREAM_TO_MASTER-CSDN博客》&#xff0c;我分享了关于 AXI Stream 接口的实际应用案例。然而&#xff0c;尽管文章中提供了代码示例&…

如何向Linux内核提交开源补丁?

2021年&#xff0c;我曾经在openEuler社区上看到一项改进Linux内核工具的需求&#xff0c;因此参与过Linux内核社区的开源贡献。贡献开源社区的流程都可以在内核社区文档中找到&#xff0c;但是&#xff0c;单独学习需要一个较长的过程&#xff0c;新手难以入门&#xff0c;因此…

分享四种免费获取SSL的方式

SSL证书目前需要部署安装的网站很多&#xff0c;主要还是基于国内目前对证书的需求度在不断的升高&#xff0c;网站多了、服务器多了之后。网络安全问题就成为了大家不得不面对的一个重要的问题了。SSL证书的作用有很多&#xff0c;这里就不一一详述了&#xff0c;本期作品主要…

如何在线阅读Linux内核源码?

开源社区有一句名言&#xff1a;Talk is cheap, show me your code。阅读源代码是学习Linux操作系统的必经之路。但是&#xff0c;Linux内核的代码量超过3000万行&#xff0c;工程包非常大&#xff0c;直接下载耗时较长&#xff0c;这就需要使用一些在线阅读的技巧。 方式1&am…