bmp 格式转 yuv420p 格式

最近研究 yuv 格式,网上搜索了很多资料,大部分代码都不能正常使用。这些代码对偶数宽高的图像都能运行良好,但是却对奇数宽高的图像并没有做相应的处理,导致运行时,数组访问越界或生成的yuv图像不能显示等问题。

经过查看 libyuv 源码,借鉴其中一些思路,写出了bmp 格式转 yuv420p 格式的代码。记录下来,以免遗忘。

转换核心代码:

inline void convert_to_yrow(const uint8_t *src, uint8_t *dst_y, int width, int depth)
{
	for (int i = 0; i < width; i++) {
		int p = i * depth;
		*dst_y++ = ((66 * src[p + 2] + 129 * src[p + 1] + 25 * src[p]) >> 8) + 16;
	}
}

inline void convert_to_uvrow(const uint8_t *src, int src_stride, uint8_t *dst_u, uint8_t *dst_v, int width, int depth)
{
	const uint8_t *src1 = src;
	const uint8_t *src2 = src + src_stride;

	for (int i = 0; i < width; i += 2) {

		int p = i * depth;

		*dst_u++ = ((-38 * src1[p + 2] - 74 * src1[p + 1] + 112 * src1[p]) >> 8) + 128;
		*dst_v++ = ((112 * src2[p + 2] - 94 * src2[p + 1] - 18 * src2[p]) >> 8) + 128;
	}
}

int convert_to_yuv420p(const uint8_t *src, int width, int height, int depth, unique_ptr<uint8_t[]> *dst)
{
	int src_stride = width * depth;

	int stride_y = width;
	int stride_uv = ALIGN(width, 2) / 2;

	int size_y = stride_y * height;
	int size_uv = stride_uv * ALIGN(height, 2) / 2;
	int size_yuv = size_y + size_uv * 2;

	dst->reset(new uint8_t[size_yuv]);

	uint8_t *yuv = (*dst).get();

	uint8_t *py = yuv;
	uint8_t *pu = py + size_y;
	uint8_t *pv = pu + size_uv;

	for (int i = 0; i < height - 1; i += 2) {

		convert_to_yrow(src, py, width, depth);
		convert_to_yrow(src + src_stride, py + stride_y, width, depth);
		convert_to_uvrow(src, src_stride, pu, pv, width, depth);

		src += src_stride * 2;
		py += stride_y * 2;
		pu += stride_uv;
		pv += stride_uv;
	}

	if (height & 1) {
		convert_to_yrow(src, py, width, depth);
		convert_to_uvrow(src, 0, pu, pv, width, depth);
	}

	return size_yuv;
}

加载bmp文件代码:

void load_image_from_bmp(const char *file, int *pwidth, int *pheight, int *pdepth, unique_ptr<uint8_t[]> *pimage_data)
{
	FILE *fin = nullptr;
	errno_t err = 0;
	if ((err = fopen_s(&fin, file, "rb")) != 0) {
		printf("Could not open input file, error code : %d\n", err);
		exit(-1);
	}

	BITMAPFILEHEADER bfh;
	int ret = fread(&bfh, 1, sizeof(bfh), fin);

	BITMAPINFOHEADER bih;
	ret = fread(&bih, 1, sizeof(bih), fin);

	int depth = bih.biBitCount / 8;
	int width_bytes = bih.biWidth * depth;
	width_bytes += width_bytes % 4;

	*pwidth = bih.biWidth;
	*pheight = bih.biHeight;
	*pdepth = depth;

	uint8_t *data = new uint8_t[bih.biSizeImage];

	ret = fread(data, 1, bih.biSizeImage, fin);

	pimage_data->reset(new uint8_t[bih.biWidth * bih.biHeight * depth]);

	for (int i = 0; i < bih.biHeight; i++) {
		for (int j = 0; j < bih.biWidth; j++) {

			int p1 = ((bih.biHeight - i - 1) * bih.biWidth + j) * 3;
			int p2 = i * width_bytes + j * depth;

			(*pimage_data)[p1] = data[p2];
			(*pimage_data)[p1 + 1] = data[p2 + 1];
			(*pimage_data)[p1 + 2] = data[p2 + 2];
		}
	}

	delete[] data;

	if (fin)
		fclose(fin);
}

完整代码如下:

#include <cstdio>
#include <cstdint>
#include <ctime>
#include <memory>
#include <Windows.h>

using namespace std;

#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1))

void load_image_from_bmp(const char *file, int *pwidth, int *pheight, int *pdepth, unique_ptr<uint8_t[]> *pimage_data)
{
	FILE *fin = nullptr;
	errno_t err = 0;
	if ((err = fopen_s(&fin, file, "rb")) != 0) {
		printf("Could not open input file, error code : %d\n", err);
		exit(-1);
	}

	BITMAPFILEHEADER bfh;
	int ret = fread(&bfh, 1, sizeof(bfh), fin);

	BITMAPINFOHEADER bih;
	ret = fread(&bih, 1, sizeof(bih), fin);

	int depth = bih.biBitCount / 8;
	int width_bytes = bih.biWidth * depth;
	width_bytes += width_bytes % 4;

	*pwidth = bih.biWidth;
	*pheight = bih.biHeight;
	*pdepth = depth;

	uint8_t *data = new uint8_t[bih.biSizeImage];

	ret = fread(data, 1, bih.biSizeImage, fin);

	pimage_data->reset(new uint8_t[bih.biWidth * bih.biHeight * depth]);

	for (int i = 0; i < bih.biHeight; i++) {
		for (int j = 0; j < bih.biWidth; j++) {

			int p1 = ((bih.biHeight - i - 1) * bih.biWidth + j) * 3;
			int p2 = i * width_bytes + j * depth;

			(*pimage_data)[p1] = data[p2];
			(*pimage_data)[p1 + 1] = data[p2 + 1];
			(*pimage_data)[p1 + 2] = data[p2 + 2];
		}
	}

	delete[] data;

	if (fin)
		fclose(fin);
}

void save_yuv(const char *file, uint8_t *yuv_data, int yuv_size)
{
	FILE *fp = nullptr;
	errno_t err = 0;
	if ((err = fopen_s(&fp, file, "wb")) != 0) {
		printf("Could not create file, error code : %d\n", err);
		exit(-1);
	}

	fwrite(yuv_data, 1, yuv_size, fp);

	if (fp)
		fclose(fp);
}

void save_y(const char *file, int width, int height, uint8_t *dst_y)
{
	int depth = 3;
	int width_bytes = width * depth;
	width_bytes += width_bytes % 4;			// 4字节对齐
	int image_size = width_bytes * height;

	BITMAPFILEHEADER bfh;

	memset(&bfh, 0, sizeof(bfh));
	bfh.bfType = 0x4d42;
	bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + image_size;
	bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	BITMAPINFOHEADER bih;

	memset(&bih, 0, sizeof(bih));
	bih.biSize = sizeof(bih);
	bih.biWidth = width;
	bih.biHeight = height;
	bih.biPlanes = 1;
	bih.biBitCount = depth * 8;
	bih.biCompression = 0;
	bih.biSizeImage = image_size;

	uint8_t *image_data = new uint8_t[image_size];

	for (int i = 0; i < height; i++) {
		for (int j = 0; j < width; j++) {
			
			int p1 = i * width_bytes + j * depth;
			int p2 = i * width + j;

			image_data[p1] = dst_y[p2];
			image_data[p1 + 1] = dst_y[p2];
			image_data[p1 + 2] = dst_y[p2];
		}
	}

	FILE *fp = nullptr;
	errno_t err = 0;
	if ((err = fopen_s(&fp, file, "wb")) != 0) {
		printf("Could not create file '%s', error code : %d\n", file, err);
		exit(-1);
	}

	int ret = fwrite(&bfh, 1, sizeof(bfh), fp);
	ret = fwrite(&bih, 1, sizeof(bih), fp);
	ret = fwrite(image_data, 1, image_size, fp);

	fflush(fp);

	if (fp)
		fclose(fp);

	delete[] image_data;
}

template<size_t dst_size>
void build_yuv_file_name(char (&dst_file_name)[dst_size], const char *src_file_name, int width, int height)
{
	char drive[5];
	char dir[100];
	char name[100];
	char ext[10];
	_splitpath_s(src_file_name, drive, dir, name, ext);

	sprintf_s(dst_file_name, "%s%s%s_%dx%d_yuv420p.yuv", drive, dir, name, width, height);
}

inline void convert_to_yrow(const uint8_t *src, uint8_t *dst_y, int width, int depth)
{
	for (int i = 0; i < width; i++) {
		int p = i * depth;
		*dst_y++ = ((66 * src[p + 2] + 129 * src[p + 1] + 25 * src[p]) >> 8) + 16;
	}
}

inline void convert_to_uvrow(const uint8_t *src, int src_stride, uint8_t *dst_u, uint8_t *dst_v, int width, int depth)
{
	const uint8_t *src1 = src;
	const uint8_t *src2 = src + src_stride;

	for (int i = 0; i < width; i += 2) {

		int p = i * depth;

		*dst_u++ = ((-38 * src1[p + 2] - 74 * src1[p + 1] + 112 * src1[p]) >> 8) + 128;
		*dst_v++ = ((112 * src2[p + 2] - 94 * src2[p + 1] - 18 * src2[p]) >> 8) + 128;
	}
}

int convert_to_yuv420p(const uint8_t *src, int width, int height, int depth, unique_ptr<uint8_t[]> *dst)
{
	int src_stride = width * depth;

	int stride_y = width;
	int stride_uv = ALIGN(width, 2) / 2;

	int size_y = stride_y * height;
	int size_uv = stride_uv * ALIGN(height, 2) / 2;
	int size_yuv = size_y + size_uv * 2;

	dst->reset(new uint8_t[size_yuv]);

	uint8_t *yuv = (*dst).get();

	uint8_t *py = yuv;
	uint8_t *pu = py + size_y;
	uint8_t *pv = pu + size_uv;

	for (int i = 0; i < height - 1; i += 2) {

		convert_to_yrow(src, py, width, depth);
		convert_to_yrow(src + src_stride, py + stride_y, width, depth);
		convert_to_uvrow(src, src_stride, pu, pv, width, depth);

		src += src_stride * 2;
		py += stride_y * 2;
		pu += stride_uv;
		pv += stride_uv;
	}

	if (height & 1) {
		convert_to_yrow(src, py, width, depth);
		convert_to_uvrow(src, 0, pu, pv, width, depth);
	}

	return size_yuv;
}

int main(int argc, char *argv[])
{
	if (argc < 2) {
		printf("Parameters is too few.\n");
		return -1;
	}

	//const char *file = "../../../images/girl1_534x800.bmp";
	//const char *file = "../../../images/1.bmp";
	//const char *file = "images/dog1.bmp";

	const char *src_file = argv[1];

	int width = 0, height = 0, depth = 0;
	unique_ptr<uint8_t[]> image_data;

	load_image_from_bmp(src_file, &width, &height, &depth, &image_data);

	printf("width : %d\n", width);
	printf("height : %d\n", height);
	printf("depth : %d\n", depth);

	// 将BGR数据转换成YUV420P

	unique_ptr<uint8_t[]> yuv;

	clock_t start = clock();

	int size_yuv = convert_to_yuv420p(image_data.get(), width, height, depth, &yuv);

	printf("elapsed time : %d\n", clock() - start);


	save_y("images/y.bmp", width, height, yuv.get());
	
	char yuv_file[1024];
	build_yuv_file_name(yuv_file, src_file, width, height);

	save_yuv(yuv_file, yuv.get(), size_yuv);


	return 0;
}

查看yuv格式的文件可以用ffmpeg的ffplay工具。

ffplay -i th2_1920x1080_yuv420p.yuv -pixel_format yuv420p -video_size 1920x1080

-pixel_format 指定图像的像素格式

-video_size 指定图像大小

发布了1 篇原创文章 · 获赞 0 · 访问量 25

猜你喜欢

转载自blog.csdn.net/yinfengnong/article/details/104115929