最简单的基于 FFmpeg 的测试样例生成工具

最简单的基于 FFmpeg 的测试样例生成工具

参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的libswscale的示例附件:测试图片生成工具

简介

本文记录一个简单的基于 FFmpeg 的测试样例生成工具。该工具可以生成视频测试时候常用的 RGB/YUV 格式的测试图片。包括灰阶测试图,彩条图,彩色条纹图,RGB 渐变彩条图,YUV 渐变彩条图,颜色视频等。下面简单介绍一下这些测试图片的生成函数。

灰阶测试图

gen_yuv420p_graybar() 是用于生成灰阶测试图的函数,该函数的定义如下:

/**
* Generate Picture contains Gray Bar changing from Black to White in YUV420P Format
*
* @param width		the width of picture.
* @param height	    the height of picture.
* @param barnum	    the number of Bars in the picture.
* @param ymin		the minimum value of luminance.
* @param ymax		the maximum value of luminance.
* @return 0 if finished, -1 if there are errors.
*/
int gen_yuv420p_graybar(int width, int height, int barnum,
	unsigned char ymin, unsigned char ymax);

简单解释每个参数的含义:

  • width:图像宽
  • height:图像高
  • barnum:灰阶数量
  • ymin:亮度最小取值
  • ymax:亮度最大取值

如果函数成功运行的话,会生成一个名称为“graybar_%dx%d_yuv420p.yuv”的 YUV420P 格式的文件(其中 %dx%d 代表了图像的宽和高)。在生成灰度图的同时,程序会打印出每一个灰阶的 YUV 取值。

例如,生成分辨率为 1280x720 的灰阶图的代码如下:

int gen_yuv420p_graybar(int width, int height, int barnum,
	unsigned char ymin, unsigned char ymax)
{

	int barwidth;
	float lum_inc;
	unsigned char lum_temp;
	int uv_width, uv_height;
	FILE *fp = NULL;
	unsigned char *data_y = NULL;
	unsigned char *data_u = NULL;
	unsigned char *data_v = NULL;
	int t = 0, i = 0, j = 0;
	char filename[100] = { 0 };

	// Check
	if (width <= 0 || height <= 0 || barnum <= 0)
	{
		printf("Error: Width, Height or Bar Number cannot be 0 or negative number.\n");
		printf("Default Param is used.\n");
		width = 640;
		height = 480;
		barnum = 10;
	}
	if (width % barnum != 0)
	{
		printf("Warning: Width cannot be divided by Bar Number without remainder.\n");
	}
	barwidth = width / barnum;
	lum_inc = ((float)(ymax - ymin)) / ((float)(barnum - 1));
	uv_width = width / 2;
	uv_height = height / 2;

	data_y = (unsigned char *)malloc(width * height);
	data_u = (unsigned char *)malloc(uv_width * uv_height);
	data_v = (unsigned char *)malloc(uv_width * uv_height);

	sprintf(filename, "graybar_%dx%d_yuv420p.yuv", width, height);
	if ((fp = fopen(filename, "wb+")) == NULL)
	{
		printf("Error: Cannot create file.");
		return -1;
	}

	// Output Info
	printf("Y, U, V value from picture's left to right:\n");
	for (t = 0; t < (width / barwidth); t++)
	{
		lum_temp = ymin + (char)(t * lum_inc);
		printf("%3d, 128, 128\n", lum_temp);
	}
	// Generate Data
	for (j = 0; j < height; j++)
	{
		for (i = 0; i < width; i++)
		{
			t = i / barwidth;
			lum_temp = ymin + (char)(t * lum_inc);
			data_y[j * width + i] = lum_temp;
		}
	}
	for (j = 0; j < uv_height; j++)
	{
		for (i = 0; i < uv_width; i++)
		{
			data_u[j * uv_width + i] = 128;
			data_v[j * uv_width + i] = 128;
		}
	}

	fwrite(data_y, width * height, 1, fp);
	fwrite(data_u, uv_width * uv_height, 1, fp);
	fwrite(data_v, uv_width * uv_height, 1, fp);
	fclose(fp);
	free(data_y);
	free(data_u);
	free(data_v);
	printf("Finish generate %s.\n", filename);
	return 0;
}

亮度取值范围为16-235:

gen_yuv420p_graybar(1280,720,10,16,235);

输出:

在这里插入图片描述

图片:

在这里插入图片描述

亮度取值范围为 0-255:

gen_yuv420p_graybar(1280,720,10,0,255);

输出:

在这里插入图片描述

图片:

在这里插入图片描述

彩条测试图

在电视节目的制作播出及设备维护中,最常用的莫过于彩条信号了。这是由于彩条信号能正确反映出各种彩色的亮度、色调和饱和度,是检验视频通道传输质量最方便的手段。

下面这张图是一张彩条测试图的示例。这张图的分辨率是 1280x720,像素格式是 RGB24,包含了电视系统中常见的“白黄青绿品红蓝黑”彩条。

在这里插入图片描述

“白黄青绿品红蓝黑”彩条中每种颜色的 RGB 取值如下所示:

颜色(R, G, B) 取值
(255, 255, 255)
(255, 255, 0)
(0, 255, 255)
绿(0, 255, 0)
(255, 0, 255)
(255, 0, 0)
(0, 0, 255)
(0, 0, 0)

很多人会奇怪,这 8 个彩条信号的顺序为什么是“白黄青绿品红蓝黑”?

其实,它们是按照它们的亮度进行排序的。

RGB 转换为 YUV 的过程中,可以通过 RGB 计算该颜色的亮度。计算的公式如下所示:

Y=0.299*R + 0.587*G + 0.114*B

把上述 8 个颜色的 R,G,B 取值带入上述公式,可以得到每种颜色的亮度取值,如下所示:

颜色(R, G, B) 取值
255
225
178
绿149
105
76
29
0

在生成彩条图像之后,程序会打印出彩条信号的颜色信息,如下图所示:

gen_rgb24_colorbar() 是用于生成彩条测试图的函数,该函数的原型如下:

/**
 * Generate Picture contains standard Color Bar in RGB24 Format
 *
 * @param width             the width of picture.
 * @param height            the height of picture.
 * @return 0 if finished, -1 if there are errors.
 */
int gen_rgb24_colorbar(int width, int height);

简单解释每个参数的含义:

  • width:图像宽
  • height:图像高

如果函数成功运行的话,会生成一个名称为“colorbar_%dx%d_rgb24.rgb”的 RGB24 格式的文件(其中 %dx%d 代表了图像的宽和高)。

函数实现:

int gen_rgb24_colorbar(int width, int height)
{

	unsigned char *data = NULL;
	int barwidth;
	char filename[100] = { 0 };
	FILE *fp = NULL;
	int i = 0, j = 0;
	int lum;
	const float r_coeff = 0.299, g_coeff = 0.587, b_coeff = 0.114;

	// Check
	if (width <= 0 || height <= 0)
	{
		printf("Error: Width, Height cannot be 0 or negative number.\n");
		printf("Default Param is used.\n");
		width = 640;
		height = 480;
	}
	if (width % 8 != 0)
	{
		printf("Warning: Width cannot be divided by Bar Number without remainder.\n");
	}

	data = (unsigned char *)malloc(width * height * 3);
	barwidth = width / 8;

	sprintf(filename, "colorbar_%dx%d_rgb24.rgb", width, height);
	if ((fp = fopen(filename, "wb+")) == NULL)
	{
		printf("Error: Cannot create file.");
		return -1;
	}

	printf("Luminance (Y) component value of colors from left to right:\n");
	lum = r_coeff * 255.0 + g_coeff * 255.0 + b_coeff * 255.0;
	printf("[White]  \tR,G,B=255,255,255\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);
	lum = r_coeff * 255.0 + g_coeff * 255.0 + b_coeff * 0.0;
	printf("[Yellow] \tR,G,B=255,255,  0\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);
	lum = r_coeff * 0.0 + g_coeff * 255.0 + b_coeff * 255.0;
	printf("[Cyan]   \tR,G,B=  0,255,255\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);
	lum = r_coeff * 0.0 + g_coeff * 255.0 + b_coeff * 0.0;
	printf("[Green]  \tR,G,B=  0,255,  0\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);
	lum = r_coeff * 255.0 + g_coeff * 0.0 + b_coeff * 255.0;
	printf("[Magenta]\tR,G,B=255,  0,255\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);
	lum = r_coeff * 255.0 + g_coeff * 0.0 + b_coeff * 0.0;
	printf("[Red]    \tR,G,B=255,  0,  0\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);
	lum = r_coeff * 0.0 + g_coeff * 0.0 + b_coeff * 255.0;
	printf("[Blue]   \tR,G,B=  0,  0,255\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);
	lum = r_coeff * 0.0 + g_coeff * 0.0 + b_coeff * 0.0;
	printf("[Black]  \tR,G,B=  0,  0,  0\t Y=%.3f*R+%.3f*G+%.3f*B=%3d\n",
		r_coeff, g_coeff, b_coeff, lum);

	for (j = 0; j < height; j++)
	{
		for (i = 0; i < width; i++)
		{
			int barnum = i / barwidth;
			switch (barnum)
			{
			case 0:
				data[(j * width + i) * 3 + 0] = 255;
				data[(j * width + i) * 3 + 1] = 255;
				data[(j * width + i) * 3 + 2] = 255;
				break;
			case 1:
				data[(j * width + i) * 3 + 0] = 255;
				data[(j * width + i) * 3 + 1] = 255;
				data[(j * width + i) * 3 + 2] = 0;
				break;
			case 2:
				data[(j * width + i) * 3 + 0] = 0;
				data[(j * width + i) * 3 + 1] = 255;
				data[(j * width + i) * 3 + 2] = 255;
				break;
			case 3:
				data[(j * width + i) * 3 + 0] = 0;
				data[(j * width + i) * 3 + 1] = 255;
				data[(j * width + i) * 3 + 2] = 0;
				break;
			case 4:
				data[(j * width + i) * 3 + 0] = 255;
				data[(j * width + i) * 3 + 1] = 0;
				data[(j * width + i) * 3 + 2] = 255;
				break;
			case 5:
				data[(j * width + i) * 3 + 0] = 255;
				data[(j * width + i) * 3 + 1] = 0;
				data[(j * width + i) * 3 + 2] = 0;
				break;
			case 6:
				data[(j * width + i) * 3 + 0] = 0;
				data[(j * width + i) * 3 + 1] = 0;
				data[(j * width + i) * 3 + 2] = 255;
				break;
			case 7:
				data[(j * width + i) * 3 + 0] = 0;
				data[(j * width + i) * 3 + 1] = 0;
				data[(j * width + i) * 3 + 2] = 0;
				break;
			}

		}
	}
	fwrite(data, width * height * 3, 1, fp);
	fclose(fp);
	free(data);
	printf("Finish generate %s.\n", filename);
	return 0;
}

例如,生成分辨率为 1280x720 的上文中的彩条图的代码如下:

gen_rgb24_colorbar(1280,720);

输出:

在这里插入图片描述

图片:

在这里插入图片描述

彩色条纹图

条纹图也是常见的一种测试图。下面这张图是一张彩色条纹图的示例。这张图的分辨率是 1280x720,像素格式是 RGB24,条纹的颜色为红色。其中竖直条纹的宽度为 1 像素,条纹之间的间隔也是 1 像素。

gen_rgb24_stripe() 是用于生成会接测试图的函数,该函数的原型如下:

/**
* Generate Picture contains Stripe in RGB24 Format
*
* @param width		the width of picture.
* @param height	    the height of picture.
* @param r			Red component of stripe
* @param g			Green component of stripe
* @param b			Blue component of stripe
* @return 0 if finished, -1 if there are errors.
*/
int gen_rgb24_stripe(int width, int height,
	unsigned char r, unsigned char g, unsigned char b);

简单解释每个参数的含义:

  • width:图像宽
  • height:图像高
  • r:条纹的 R 分量取值
  • g:条纹的 G 分量取值
  • b:条纹的 B 分量取值

如果函数成功运行的话,会生成一个名称为“rgbstripe_%dx%d_rgb24.rgb”的RGB24格式的文件(其中 %dx%d 代表了图像的宽和高)。

例如,生成分辨率为 1280x720 的上文中的彩色条纹图的代码如下:

gen_rgb24_stripe(1280,720,255,0,0);

函数实现:

int gen_rgb24_stripe(int width, int height,
	unsigned char r, unsigned char g, unsigned char b)
{

	unsigned char *data = NULL;
	char filename[100] = { 0 };
	FILE *fp = NULL;
	int i = 0, j = 0;

	// 检查输入参数
	if (width <= 0 || height <= 0)
	{
		printf("Error: Width, Height cannot be 0 or negative number.\n");
		printf("Default Param is used.\n");
		width = 640;
		height = 480;
	}

	data = (unsigned char *)malloc(width * height * 3);

	sprintf(filename, "rgbstripe_%dx%d_rgb24.rgb", width, height);
	if ((fp = fopen(filename, "wb+")) == NULL)
	{
		printf("Error: Cannot create file.");
		return -1;
	}

	for (j = 0; j < height; j++)
	{
		for (i = 0; i < width; i++)
		{
			if (i % 2 != 0)
			{
				// 输入颜色条纹
				data[(j * width + i) * 3 + 0] = r;
				data[(j * width + i) * 3 + 1] = g;
				data[(j * width + i) * 3 + 2] = b;
			}
			else
			{
				// 白色条纹
				data[(j * width + i) * 3 + 0] = 255;
				data[(j * width + i) * 3 + 1] = 255;
				data[(j * width + i) * 3 + 2] = 255;
			}
		}
	}
	fwrite(data, width * height * 3, 1, fp);
	fclose(fp);
	free(data);
	printf("Finish generate %s.\n", filename);
	return 0;
}

图片:

在这里插入图片描述

RGB 渐变彩条图

下面这张图是一张 RGB 渐变彩条图的示例。这张图的分辨率是 1280x720,一共包含了 10 个彩条,像素格式是 RGB24,RGB 颜色从红色(RGB 分别取值为 255,0,0)逐渐变换为蓝色(RGB 分别取值为 0,0,255)。

在这里插入图片描述

每个彩条的RGB取值如下所列:

255,   0,   0
227,   0,  28
199,   0,  56
170,   0,  85
142,   0, 113
114,   0, 141
 85,   0, 170
 57,   0, 198
 29,   0, 226
  0,   0, 255

gen_rgb24_rgbgradient_bar () 是用于生成渐变彩条图的函数,该函数的原型如下:

/**
* Generate Picture contains Color Bar Changing from source color
* to destination color in RGB24 Format
*
* @param width		the width of picture.
* @param height	    the height of picture.
* @param barnum	    the number of Bars in the picture.
* @param src_r		Red component of source color.
* @param src_g		Green component of source color.
* @param src_b		Blue component of source color.
* @param dst_r		Red component of destination color.
* @param dst_g		Green component of destination color.
* @param dst_b		Blue component of destination color.
* @return 0 if finished, -1 if there are errors.
*/
int gen_rgb24_rgbgradient_bar(int width, int height, int barnum,
	unsigned char src_r, unsigned char src_g, unsigned char src_b,
	unsigned char dst_r, unsigned char dst_g, unsigned char dst_b);

简单解释每个参数的含义:

  • width:图像宽
  • height:图像高
  • barnum:彩条数量
  • src_r:左侧颜色 R 分量
  • src_g:左侧颜色 G 分量
  • src_b:左侧颜色 B 分量
  • dst_r:右侧颜色 R 分量
  • dst_g:右侧颜色 G 分量
  • dst_b:右侧颜色 B 分量

函数实现:

int gen_rgb24_rgbgradient_bar(int width, int height, int barnum,
	unsigned char src_r, unsigned char src_g, unsigned char src_b,
	unsigned char dst_r, unsigned char dst_g, unsigned char dst_b)
{

	unsigned char *data = NULL;
	int barwidth;
	float r_inc, g_inc, b_inc;
	unsigned char r_temp, g_temp, b_temp;
	char filename[100] = { 0 };
	FILE *fp = NULL;
	int t = 0, i = 0, j = 0;

	// Check
	if (width <= 0 || height <= 0 || barnum <= 0)
	{
		printf("Error: Width, Height or Bar Number cannot be 0 or negative number.\n");
		printf("Default Param is used.\n");
		width = 640;
		height = 480;
	}
	if (width % barnum != 0)
	{
		printf("Warning: Width cannot be divided by Bar Number without remainder.\n");
	}


	data = (unsigned char *)malloc(width * height * 3);
	barwidth = width / barnum;
	r_inc = ((float)(dst_r - src_r)) / ((float)(barnum - 1));
	g_inc = ((float)(dst_g - src_g)) / ((float)(barnum - 1));
	b_inc = ((float)(dst_b - src_b)) / ((float)(barnum - 1));

	sprintf(filename, "rgbgradientbar_%dx%d_rgb24.rgb", width, height);
	if ((fp = fopen(filename, "wb+")) == NULL)
	{
		printf("Error: Cannot create file.");
		return -1;
	}

	// Output Info
	printf("R, G, B value from picture's left to right:\n");
	for (t = 0; t < (width / barwidth); t++)
	{
		r_temp = src_r + (char)(t * r_inc);
		g_temp = src_g + (char)(t * g_inc);
		b_temp = src_b + (char)(t * b_inc);
		printf("%3d, %3d, %3d\n", r_temp, g_temp, b_temp);
	}

	for (j = 0; j < height; j++)
	{
		for (i = 0; i < width; i++)
		{
			t = i / barwidth;
			r_temp = src_r + (char)(t * r_inc);
			g_temp = src_g + (char)(t * g_inc);
			b_temp = src_b + (char)(t * b_inc);
			data[(j * width + i) * 3 + 0] = r_temp;
			data[(j * width + i) * 3 + 1] = g_temp;
			data[(j * width + i) * 3 + 2] = b_temp;
		}
	}
	fwrite(data, width * height * 3, 1, fp);
	fclose(fp);
	free(data);
	printf("Finish generate %s.\n", filename);
	return 0;
}

如果函数成功运行的话,会生成一个名称为“rgbgradientbar_%dx%d_rgb24.rgb”的 RGB24 格式的文件(其中 %dx%d 代表了图像的宽和高)。

例如,生成分辨率为 1280x720 的上文中的渐变彩条图的代码如下:

gen_rgb24_rgbgradient_bar(1280,720,10,255,0,0,0,0,255);

在生成渐变彩条图像之后,程序会打印出彩条信号的颜色信息,如下图所示:

在这里插入图片描述

图片:

在这里插入图片描述

YUV 渐变彩条图

下面这张图是一张 YUV 渐变彩条图的示例。这张图的分辨率是 1280x720,一共包含了 10 个彩条,像素格式是 YUV420P,YUV 颜色从绿色(YUV 分别取值为 0,0,0)逐渐变换为灰色(YUV 分别取值为 128,128,128)。

在这里插入图片描述

每个彩条的YUV取值如下所列:

 0,   0,   0
 14,  14,  14
 28,  28,  28
 42,  42,  42
 56,  56,  56
 71,  71,  71
 85,  85,  85
 99,  99,  99
113, 113, 113
128, 128, 128

gen_yuv420p_yuvgradient_bar() 是用于生成渐变彩条图的函数,该函数的原型如下:

/**
* Generate Picture contains Color Bar Changing from source color
* to destination color in YUV420P Format
*
* @param width		the width of picture.
* @param height	    the height of picture.
* @param barnum	    the number of Bars in the picture.
* @param src_y		Luma component of source color.
* @param src_u		U component of source color.
* @param src_v		V component of source color.
* @param dst_y		Luma component of destination color.
* @param dst_u		U component of destination color.
* @param dst_v		V component of destination color.
* @return 0 if finished, -1 if there are errors.
*/
int gen_yuv420p_yuvgradient_bar(int width, int height, int barnum,
	unsigned char src_y, unsigned char src_u, unsigned char src_v,
	unsigned char dst_y, unsigned char dst_u, unsigned char dst_v);

简单解释每个参数的含义:

  • width:图像宽
  • height:图像高
  • barnum:彩条数量
  • src_y:左侧颜色 Y 分量
  • src_u:左侧颜色 U 分量
  • src_v:左侧颜色 V 分量
  • dst_y:右侧颜色 Y 分量
  • dst_u:右侧颜色 U 分量
  • dst_v:右侧颜色 V 分量

如果函数成功运行的话,会生成一个名称为“yuvgradientbar_%dx%d_yuv420p.yuv”的 YUV420P 格式的文件(其中 %dx%d 代表了图像的宽和高)。

例如,生成分辨率为 1280x720 的上文中的渐变彩条图的代码如下:

gen_yuv420p_yuvgradient_bar(1280,720,10,0,0,0,128,128,128);

在生成渐变彩条图像之后,程序会打印出彩条信号的颜色信息,如下图所示:

在这里插入图片描述

图片:

在这里插入图片描述

颜色视频

RGB 颜色视频

“RGB颜色视频”不是一幅图像,而是一段视频文件。这个视频中包含了 RGB24 中的所有颜色。通过这个视频,可以了解 RGB 各个分量对颜色的影响。下面简单记录一下这个视频的规则:

  • 视频的宽为 256,高为 256,视频的帧数为 256;
  • 最左边的像素的 R 分量取值为 0,从左至右每个像素的 R 分量的取值依次加 1;
  • 最上面的像素的 G 分量取值为 0,从上至下每个像素的 G 分量的取值依次加 1;
  • 第 1 帧的所有像素的 B 分量取值为 0,每增加一帧该帧像素的 B 分量的取值依次加 1。

所以可以理解为一个坐标系,原点在视频的左上角,X 轴对应 R 分量,Y 轴对应 G 分量,Z 轴(时间轴)对应 B 分量。

该视频的第 0 帧如下图所示:

在这里插入图片描述

从图中可以看出,左上角为黑色(R,G,B 取值 0,0,0);右上角为红色(R,G,B 取值 0,0,255);左下角为绿色(R,G,B 取值 0,255,0);右下角为黄色(R,G,B 取值 255,255,0)。

该视频的第 128 帧如下图所示:

在这里插入图片描述

可以看出当蓝色分量增加至 128 的时候,颜色发生了较大的变化。

该视频的第 255 帧如下图所示:

在这里插入图片描述

从图中可以看出,左上角为蓝色(R,G,B 取值 0,0,255);右上角为品色(R,G,B 取值 255,0,255);左下角为青色(R,G,B 取值 0,255,255);右下角为白色(R,G,B 取值 255,255,255)。

YUV 颜色视频

“RGB颜色视频”中包含了 YUV444 中的所有颜色。通过这个视频,可以了解 YUV 各个分量对颜色的影响。下面简单记录一下这个视频的规则:

  • 视频的宽为 256,高为 256,视频的帧数为 256;
  • 最左边的像素的 U 分量取值为 0,从左至右每个像素的 U 分量的取值依次加 1;
  • 最上面的像素的V分量取值为 0,从上至下每个像素的 V 分量的取值依次加 1;
  • 第1帧的所有像素的 Y 分量取值为 0,每增加一帧该帧像素的 Y 分量的取值依次加 1;

所以可以理解为一个坐标系,原点在视频的左上角,X 轴对应 U 分量,Y 轴对应 V 分量,Z 轴(时间轴)对应 Y 分量。

该视频的第 0 帧如下图所示:

在这里插入图片描述

从图中可以看出,左上角颜色偏绿(Y,U,V 取值 0,0,0);右上角颜色偏蓝(Y,U,V 取值 0,0,255);左下角颜色偏红(Y,U,V 取值 0,255,0);右下角颜色偏品色(Y,U,V 取值 255,255,0)。而正中央是黑色(Y,U,V 取值 0,128,128)。

在这个地方可能很多人会有疑问,认为 Y,U,V 取值为 0,0,0 的时候按理说应该是黑色。实际上 U,V 是加了偏置的分量,而偏置量就是 128。所以“纯正”的黑色实际上对应的是 Y,U,V 取值为 0,128,128 的颜色。

该视频的第 128 帧如下图所示:

在这里插入图片描述

可以看出随着 Y 分量的增加,颜色发生了一些变化。

该视频的第 255 帧如下图所示:

在这里插入图片描述

可以看出,尽管 Y 分量从 0 增长到 255,但是实际上色调变化不大,只是亮度变化很大。这是因为 U,V 分量存储了色度信息,Y 分量存储了亮度信息。

函数原型与实现

函数原型:

/**
* Generate a video in 256x256 and has 256 frames that contains all the colors.
* Each color is shown in 1 pixel. They are mapped as follows:
* In RGB24:
* R component's value is increasing with the growth of width (X-axis);
* G component's value is increasing with the growth of height (Y-axis);
* B component's value is increasing with the growth of frame number (Z-axis).
* In YUV444P:
* U component's value is increasing with the growth of width (X-axis);
* V component's value is increasing with the growth of height (Y-axis);
* Y component's value is increasing with the growth of frame number (Z-axis).
*
* This function now support to draw YUV444P/RGB24 format pixel.
*
* @return 0 if finished, -1 if there are errors.
*/
int gen_allcolor_video();

函数实现:

int gen_allcolor_video()
{

	unsigned char *data = NULL;
	char filename[100] = { 0 };
	FILE *fp = NULL;
	int width = 256, height = 256, frames = 256;
	int i = 0, j = 0, k = 0;

	// From left to right (width, X-axis), R increasing from 0 to 255 
	// From Top to bottom (height, Y-axis), G increasing from 0 to 255 
	// From 0 to 255 frames (time, Z-axis), B increasing from 0 to 255 
	data = (unsigned char *)malloc(width * height * 3);
	sprintf(filename, "allcolor_xr_yg_zb_%dx%d_rgb24.rgb", width, height);
	if ((fp = fopen(filename, "wb+")) == NULL)
	{
		printf("Error: Cannot create file.");
		return -1;
	}

	// Generate Data
	for (k = 0; k < frames; k++)
	{
		for (j = 0; j < height; j++)
		{
			for (i = 0; i < width; i++)
			{
				data[(j * width + i) * 3 + 0] = i;
				data[(j * width + i) * 3 + 1] = j;
				data[(j * width + i) * 3 + 2] = k;
			}
		}
		fwrite(data, width * height * 3, 1, fp);
		printf("Finish generate frame %d.\n", k);
	}
	fclose(fp);
	free(data);
	printf("Finish generate %s.\n", filename);

	// From left to right (width, X-axis), U increasing from 0 to 255 
	// From Top to bottom (height, Y-axis), V increasing from 0 to 255 
	// From 0 to 255 frames (time, Z-axis), Y increasing from 0 to 255 
	data = (unsigned char *)malloc(width*height);
	sprintf(filename, "allcolor_xu_yv_zy_%dx%d_yuv444p.yuv", width, height);
	if ((fp = fopen(filename, "wb+")) == NULL)
	{
		printf("Error: Cannot create file.");
		return -1;
	}

	// Generate Data
	for (k = 0; k < frames; k++)
	{
		for (j = 0; j < height; j++)
		{
			// Y
			for (i = 0; i < width; i++)
			{
				data[j * width + i] = k;
			}
		}
		fwrite(data, width * height, 1, fp);
		for (j = 0; j < height; j++)
		{
			// U
			for (i = 0; i < width; i++)
			{
				data[j * width + i] = i;
			}
		}
		fwrite(data, width * height, 1, fp);
		for (j = 0; j < height; j++)
		{
			// V
			for (i = 0; i < width; i++)
			{
				data[j * width + i] = j;
			}
		}
		fwrite(data, width * height, 1, fp);
		printf("Finish generate frame %d.\n", k);
	}
	fclose(fp);
	free(data);
	printf("Finish generate %s.\n", filename);

	return 0;
}

该函数没有参数,直接调用即可生成上述视频。

输出:

在这里插入图片描述

工具函数:RGB24 转 BMP

本工具除了可以生成测试图片外,还提供了一个简单的工具函数:RGB24 转 BMP。

经过转换后,原本只能用专用的 RGB/YUV 播放器查看的像素数据,就可以直接拿图片浏览器查看了。

RGB24 转换 BMP 有以下 2 个关键点:

  1. 在 RGB 数据前面加上文件头;
  2. 把 RGB24 数据中的“R”和“B”位置互换(因为 BMP 中的 RGB24 实际的存储方式是 bgrbgrbgr…)。

函数原型:

/**
* Convert RGB24 format to BMP format
*
* @param rgb24path		path of input RGB24 file.
* @param bmppath		path of output BMP file
* @param width			the width of picture.
* @param height		    the height of picture.
* @return 0 if finished, -1 if there are errors.
*/
int rgb24_to_bmp(char *rgb24path, char *bmppath, int width, int height);

简单解释每个参数的含义:

  • rgb24path:输入 rgb 文件路径
  • bmppath:输出 bmp 图片文件路径
  • width:图像宽
  • height:图像高

函数实现:

int rgb24_to_bmp(char *rgb24path, char *bmppath, int width, int height)
{
	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, 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, *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;

	// Write file head
	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++)
		{
			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;
}

例如输入的 RGB24 像素数据如下所示:

在这里插入图片描述

而输出的 BMP 图片如下所示:

在这里插入图片描述

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Sample-Generator

CSDN:Simplest FFmpeg Sample Generator.zip

  • 46
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值