最近研究 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 指定图像大小