最简单的基于 FFmpeg 的 libswscale 的示例(YUV 转 RGB)
最简单的基于 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 个关键点:
- 在 RGB 数据前面加上文件头;
- 把 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 图片如下所示: