一、内容拆解
-
选择的是实验内容①:自行生成多个BMP文件(24bit),至少包含5个不同场景画面,将多个BMP文件转换为YUV文件,并要求在命令行设置每个画面出现的帧数,YUV文件至少包含200帧,调试程序并用播放器观看。
-
考察点:
①BMP文件格式是怎样的,我们转换前要获得哪些数据?=>BMP文件位图大小、每个像素的RGB值及在文件中的排列位置
②BMP转YUV有直接的方法吗?没有的话,需要用之前的 RGB作为中介吗?BMP怎么转RGB呢?
③怎样在命令行设置画面帧数呢?
二、思路概述
基本思路框图
1. bmp文件格式分析
BMP(全称 Bitmap)是 Windows 操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用广泛。它采用位映射存储格式,除了图像深度可选以外,在绝大多数应用中不采用其他任何压缩,因此,BMP 文件所占用的空间很大。
BMP 文件的图像深度可选 lbit、4bit、8bit、16bit 及 24bit。BMP 文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。
由于 BMP 文件格式是 Windows 环境中交换与图有关的数据的一种标准,因此在 Windows 环境中运行的图形图像软件都支持 BMP 图像格式。
以0.bmp为例,我们简单看看BMP文件格式:
(1)位图头文件数据结构,占14个字节,它包含 BMP 图像文件的类型、显示内容等信息;!(2)位图信息数据结构,占40个字节,它包含有 BMP 图像的宽、高、压缩方法,以及定义颜色等信息;
(3)调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的 BMP)就不需要调色板;
(4)位图数据,这部分的内容根据 BMP 位图使用的位数不同而不同,在 24 位图中直接使用 RGB,而其他的小于 24 位的使用调色板中颜色索引值。
2. bmp => rgb
过程:读入BMP文件,声明变量,开辟存储空间。从BMP文件头和信息头读入之位图的宽高、bit数等信息,开辟相应空间,定义两个指针:BITMAPFILEHEADER File_header
; BITMAPINFOHEADER Info_header
来分别用来获取文件头和信息头的数据。这两种类型需要加<windows.h>头文件。从24bit位图数据直接提取出RGB数据,写入缓冲区,图像数据需倒置,否则生成的序列播放将是倒向。
3. yuv序列实现
由RGB转YUV,生成YUV视频循环写入,每张图片素材40帧(后期为了录制较短视频修改为12帧粘贴在下),总共200帧。
三、关键代码
- 文件声明部分
#include <stdio.h>
#include<windows.h>
#include<math.h>
#define u_int8_t unsigned __int8
#define u_int unsigned __int32
#define u_int32_t unsigned __int32
using namespace std;
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
float RGBYUV02990[256], RGBYUV05870[256], RGBYUV01140[256];
float RGBYUV01684[256], RGBYUV03316[256];
float RGBYUV04187[256], RGBYUV00813[256];
//变量初始化
u_int frameWidth = 256;
u_int frameHeight = 256;
bool flip = TRUE;
u_int size, i, k, j, t = 0;
char* a = NULL;
char* bmpFileName = NULL;
char* yuvFileName = NULL;
FILE* bmpFile = NULL;
FILE* yuvFile = NULL;
u_int8_t* rgbBuf = NULL;
u_int8_t* yBuf = NULL;
u_int8_t* uBuf = NULL;
u_int8_t* vBuf = NULL;
u_int32_t videoFramesWritten = 0;
//命令行参数
k = atoi(argv[6]);//图像重复帧数
yuvFileName = argv[7];
- 调色板判断
bool MakePalette(FILE* pFile, BITMAPFILEHEADER& file_h, BITMAPINFOHEADER& info_h, RGBQUAD* pRGB_out)
{
/*如果图像开始位置与信息头结束的位置中间,还有2的info_h.biBitCount次方(颜色数)个RGBUAQ空间,则存在调色板*/
if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD) * pow(2, (float)info_h.biBitCount))
{
fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
fread(pRGB_out, sizeof(RGBQUAD), (unsigned int)pow(2, (float)info_h.biBitCount), pFile);
return true;
}
else
return false;
}
- BMP2RGB
//bmp转rgb
void ReadRGB(BITMAPFILEHEADER& file_h, BITMAPINFOHEADER& info_h, FILE* bmpFile, unsigned char* rgbDataOut)
{
unsigned long Loop, i, j;
unsigned char mask = 0;
unsigned char* Data;
unsigned long width, height;
/*计算实际的宽高*/
//由于需与DWORD对齐,所以每行需满足为4字节的整数倍,若不是则需补成其整数倍
if (((info_h.biWidth * info_h.biBitCount) % 4) == 0)
width = info_h.biWidth / 8 * info_h.biBitCount;
else
width = (info_h.biWidth * info_h.biBitCount + 31) / 32 * 4;
//判断高是否为偶数,若不是,补齐
if ((info_h.biHeight % 2) == 0)
height = info_h.biHeight;
else
height = info_h.biHeight + 1;
Data = (unsigned char*)malloc(height * width);
//写入数据到Data中
fseek(bmpFile, file_h.bfOffBits, 0);
if (fread(Data, height * width, 1, bmpFile) != 1)
{
printf("read file error!");
exit(0);
}
RGBQUAD* pRGB = (RGBQUAD*)malloc(sizeof(RGBQUAD) * (unsigned int)pow(2, (float)info_h.biBitCount));//把unsigned char 改为unsigned int,否则溢出
if (!MakePalette(bmpFile, file_h, info_h, pRGB))
printf("No palette!");
//深度为24bit时,不需要调用调色板
if (info_h.biBitCount == 24)
{
memcpy(rgbDataOut, Data, height * width);//将Data中的数据直接拷到rgbDataOut
free(Data);
return;
}
free(Data);
free(pRGB);
}
- YUV循环写入
//write yuv file
for (i = 0; i < k; i++)
{
fwrite(yBuf, 1, frameWidth * frameHeight, yuvFile);
fwrite(uBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
fwrite(vBuf, 1, (frameWidth * frameHeight) / 4, yuvFile);
printf("\r...%d", ++videoFramesWritten);
}
- 主程序调用
yuvFile = fopen(yuvFileName, "wb");
if (yuvFile == NULL)
{
printf("cannot find yuv file\n");
exit(1);
}
else
{
printf("The output yuv file is %s\n", yuvFileName);
}
for (j = 1; j < 6; j++)
{
bmpFileName = argv[j];
/* open the bmp file */
bmpFile = fopen(bmpFileName, "rb");
if (bmpFile == NULL)
{
printf("cannot find bmp file\n");
exit(1);
}
else
{
printf("The input bmp file is %s\n", bmpFileName);
}
//读取位图文件头
if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
{
printf("read file header error!");
exit(0);
}
if (File_header.bfType != 0x4D42)
{
printf("Not bmp file!");
exit(0);
}
else
{
a = (char*)&File_header.bfType;
printf("this is a %s\n", a);
//printf("this is a %x\n",file_h.bfType);
}
//读取信息头
if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
{
printf("read info header error!\n\n");
return false;
}
// end read header
frameWidth = Info_header.biWidth;
frameHeight = Info_header.biHeight;
size = frameWidth * frameHeight;
//开辟缓冲区
rgbBuf = (u_int8_t*)malloc(size * 3);
yBuf = (u_int8_t*)malloc(size);
uBuf = (u_int8_t*)malloc(size / 4);
vBuf = (u_int8_t*)malloc(size / 4);
//从BMP文件中读取RGB
ReadRGB(File_header, Info_header, bmpFile, rgbBuf);
//RGB转YUV
if (RGB2YUV(frameWidth, frameHeight, rgbBuf, yBuf, uBuf, vBuf, flip))
{
printf("error");
return 0;
}
四、总结反思
结果示例:
yuv录屏
整体效果还可以,为了限制大小选择了每张图片12帧的形式。