来源:http://blog.csdn.net/jiandan524/article/details/57452211
简介
数字图像处理的过程中,YUV文件是比较常见的视频源数据。本文根据的项目的需要,设计了一款入门级的YUV播放器。该播放器可以支持UYVY、I420这两种YUV文件,通过YUV2RGB将YUV文件转换成DIB位图,从而进行播放显示。
YUV采样格式
和RGB颜色空间相比,YUV颜色空间充分利用了人眼的特性,人的眼睛对亮度的敏感度远大于色度。在保证基本画质的前提下,可以对一幅画面的色度分量进行删减。下面三张图片是常见的三种YUV采样方式,YUV4:4:4、YUV4:2:2、YUV4:2:0。其中,YUV4:4:4是一种无压缩的采样方式,每一个Y分量对应一组UV分量;YUV4:2:2的采样方式丢弃了一半的色度分量,每两个Y分量对应一组UV分量;YUV4:2:0的采样方式对齐了四分之三的色度分量,每四个Y分量对应一组UV分量。
YUV文件格式之UYVY和I420
目前的项目中用到了UYVY和I420这两种YUV文件,为了方便自己使用,因而自己写了一个入门级的YUV播放器。下面以一个8x4的图片来说明UYVY和I420的文件存储格式。
<1> UYVY文件
UYVY文件是YUV4:2:2采样格式的一种存储格式,具体的地址对应关系如下图所示,U、Y、V、Y依次按顺序存储在文件中。
<2> I420文件
I420文件是YUV4:2:0采样格式的一种存储格式,具体的地址对应关系如下图所示,和UYVY文件稍有不同。I420文件先存储Y块、然后是U块和V块。每个Y/U/V块块内的的像素依次存储在文件中。
色域转换
YUV文件方便了数字图像的处理,但是不能直接用于显示,在基于GDI的播放器中,我们仍然需要先将YUV转换为RGB,然后给它按上一个DIB的文件头,然后才能显示。
本文采用的转换公式如下:
R =1.164*(Y-16) + 1.596*(V-128)
G =1.164*(Y-16) - 0.813*(V-128) - 0.392*(U-128)
B =1.164*(Y-16) + 2.017*(U-128)
具体实践的时候,我又将这些浮点数扩大1024倍变为定点数,然后再做处理,经过处理后的公式如下:
R =(1192*(Y-16) + 1634*(V-128))/1024
G =(1192*(Y-16) - 833*(V-128) -401*(U-128))/1024
B =(1192*(Y-16) + 2065*(U-128))/1024
程序中所采用的代码如下:
-
-
-
-
-
-
-
-
-
-
- void yuv2rgb(BYTE y, BYTE u, BYTE v, BYTE *r, BYTE *g, BYTE *b)
- {
- int y0,u0,v0;
- int m0,m1,m2,m3,m4;
- int r0,g0,b0;
-
- y0 = y - 16;
- u0 = u - 128;
- v0 = v - 128;
-
- m0 = 1192*y0;
- m1 = 1634*v0;
- m2 = 833 *v0;
- m3 = 401 *u0;
- m4 = 2065*u0;
-
- r0 = (m0 + m1) >> 10;
- g0 = (m0 - m2 - m3) >> 10;
- b0 = (m0 + m4) >> 10;
-
- if(r0 > 255)
- *r = 255;
- else if(r0 < 0)
- *r = 0;
- else
- *r = r0;
-
- if(g0 > 255)
- *g = 255;
- else if(g0 < 0)
- *g = 0;
- else
- *g = g0;
-
- if(b0 > 255)
- *b = 255;
- else if(b0 < 0)
- *b = 0;
- else
- *b = b0;
- }
渲染函数
程序采用了一个单独的渲染函数来完成绘图的工作,如下面的代码所示,渲染函数依次完成了动态内存分配,YUV图片读取、色域转换、BITMAPINFO添加以及位图显示。这里需要注意的是BITMAPINFO的biHeight需要设置成负数,因为我们的RGB数据是按照从上到下,从左到右的方式存储的。
消息处理函数
本文所采用的消息处理函数主要完成文件的操作、帧率的控制、以及画面的渲染。其中渲染函数在WM_PAINT和WM_TIMER中都被调用。大部分时间的显示由WM_TIMER控制,这样的好处是避免了WM_PAINT调用时的窗口清除,避免了画面的闪烁。
演示效果
下图为本程序的演示效果:
源代码
完整的源代码如下,用户如果想用这段代码,需要做一些前提准备和修改。用户需要准备一个UYVY或者I420文件,并根据文件的属性修改
YUV_FILE_NAME
、
VIDEO_WIDTH
、
VIDEO_HEIGHT
、
YUV_TYPE
。
-
-
-
-
-
-
-
-
-
-
-
-
- #include <Windows.h>
- #include <math.h>
- #include <stdio.h>
- #include <stdlib.h>
-
- const char g_szClassName[] = "myYuvViewClass";
-
-
- #define T_UYVY 0
-
-
-
- #define T_I420 1
-
-
-
-
-
-
- #define YUV_FILE_NAME "sunflower_720p.uyvy" // YUV文件名称
- #define VIDEO_WIDTH 1280 // 视频宽度
- #define VIDEO_HEIGHT 720 // 视频高度
- #define YUV_TYPE T_UYVY // YUV文件类型
-
- HDC hdc;
- PAINTSTRUCT ps;
-
- FILE *yuv_file;
- fpos_t yuv_file_len;
- fpos_t yuv_file_pos;
-
- unsigned int iyuv_type;
-
-
-
-
-
-
-
-
-
-
-
- void yuv2rgb(BYTE y, BYTE u, BYTE v, BYTE *r, BYTE *g, BYTE *b)
- {
- int y0,u0,v0;
- int m0,m1,m2,m3,m4;
- int r0,g0,b0;
-
- y0 = y - 16;
- u0 = u - 128;
- v0 = v - 128;
-
- m0 = 1192*y0;
- m1 = 1634*v0;
- m2 = 833 *v0;
- m3 = 401 *u0;
- m4 = 2065*u0;
-
- r0 = (m0 + m1) >> 10;
- g0 = (m0 - m2 - m3) >> 10;
- b0 = (m0 + m4) >> 10;
-
- if(r0 > 255)
- *r = 255;
- else if(r0 < 0)
- *r = 0;
- else
- *r = r0;
-
- if(g0 > 255)
- *g = 255;
- else if(g0 < 0)
- *g = 0;
- else
- *g = g0;
-
- if(b0 > 255)
- *b = 255;
- else if(b0 < 0)
- *b = 0;
- else
- *b = b0;
- }
-
-
-
-
- void Render(HWND hwnd)
- {
- int i,j;
-
- BYTE *yuv;
- BYTE *rgb;
-
- BYTE y,u,v;
- BYTE r,g,b;
-
- BITMAPINFO dibInfo;
-
-
- rgb = (BYTE *)malloc(sizeof(BYTE)*VIDEO_WIDTH*VIDEO_HEIGHT*3);
-
-
- if(iyuv_type == T_UYVY)
- {
- yuv = (BYTE *)malloc(sizeof(BYTE)*VIDEO_WIDTH*VIDEO_HEIGHT*2);
- fread(yuv,sizeof(BYTE),VIDEO_WIDTH*VIDEO_HEIGHT*2,yuv_file);
- }
-
- if(iyuv_type == T_I420)
- {
- yuv = (BYTE *)malloc(sizeof(BYTE)*VIDEO_WIDTH*VIDEO_HEIGHT*1.5);
- fread(yuv,sizeof(BYTE),VIDEO_WIDTH*VIDEO_HEIGHT*1.5,yuv_file);
- }
-
-
- for(i=0; i<VIDEO_HEIGHT; i++)
- for(j=0; j<VIDEO_WIDTH; j++)
- {
- if(iyuv_type == T_UYVY)
- {
- y = yuv[i*VIDEO_WIDTH*2 + j*2 + 1];
- u = yuv[i*VIDEO_WIDTH*2 + (j/2)*4];
- v = yuv[i*VIDEO_WIDTH*2 + (j/2)*4 + 2];
- }
-
- if(iyuv_type == T_I420)
- {
- y = yuv[i*VIDEO_WIDTH + j];
- u = yuv[VIDEO_WIDTH*VIDEO_HEIGHT + (i>>1)*VIDEO_WIDTH/2 + (j>>1)];
- v = yuv[VIDEO_WIDTH*VIDEO_HEIGHT*5/4 + (i>>1)*VIDEO_WIDTH/2 + (j>>1)];
- }
- yuv2rgb(y,u,v,&r,&g,&b);
-
- rgb[i*VIDEO_WIDTH*3+j*3+2] = r;
- rgb[i*VIDEO_WIDTH*3+j*3+1] = g;
- rgb[i*VIDEO_WIDTH*3+j*3] = b;
- }
-
-
- dibInfo.bmiHeader.biSize = sizeof(BITMAPINFO);
- dibInfo.bmiHeader.biWidth = VIDEO_WIDTH;
- dibInfo.bmiHeader.biHeight = -VIDEO_HEIGHT;
- dibInfo.bmiHeader.biPlanes = 1;
- dibInfo.bmiHeader.biBitCount = 24;
- dibInfo.bmiHeader.biCompression = 0;
- dibInfo.bmiHeader.biSizeImage = VIDEO_WIDTH*VIDEO_HEIGHT*3;
- dibInfo.bmiHeader.biXPelsPerMeter = 0x0ec4;
- dibInfo.bmiHeader.biYPelsPerMeter = 0x0ec4;
- dibInfo.bmiHeader.biClrUsed = 0;
- dibInfo.bmiHeader.biClrImportant = 0;
-
- int nResult = StretchDIBits(
- hdc,
- 0, 0,
- VIDEO_WIDTH, VIDEO_HEIGHT,
- 0, 0,
- VIDEO_WIDTH, VIDEO_HEIGHT,
- rgb,
- &dibInfo,
- DIB_RGB_COLORS,
- SRCCOPY);
-
-
- free(rgb);
- free(yuv);
- }
-
-
-
-
- LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch(msg)
- {
- case WM_CREATE:
-
- iyuv_type = YUV_TYPE;
- if((yuv_file=fopen(YUV_FILE_NAME,"rb")) == NULL)
- {
- MessageBox(NULL, TEXT("YUV文件打开失败!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
- exit(0);
- }
-
- fseek(yuv_file, 0, SEEK_END);
- fgetpos(yuv_file,&yuv_file_len);
- fseek(yuv_file, 0, SEEK_SET);
-
- if(iyuv_type == T_UYVY)
- {
- if(yuv_file_len < VIDEO_WIDTH*VIDEO_HEIGHT*2)
- {
- MessageBox(NULL,TEXT("YUV数据不足一帧!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
- exit(0);
- }
- }
-
- if(iyuv_type == T_I420)
- {
- if(yuv_file_len < VIDEO_WIDTH*VIDEO_HEIGHT*1.5)
- {
- MessageBox(NULL,TEXT("YUV数据不足一帧!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
- exit(0);
- }
- }
-
-
- SetTimer(hwnd,0,30,0);
- break;
-
-
- case WM_TIMER:
-
- hdc = GetDC(hwnd);
- fgetpos(yuv_file,&yuv_file_pos);
-
- if(iyuv_type == T_UYVY)
- {
- if(yuv_file_len - yuv_file_pos < VIDEO_WIDTH*VIDEO_HEIGHT*2)
- {
- fseek(yuv_file, 0, SEEK_SET);
- }
- }
-
- if(iyuv_type == T_I420)
- {
- if(yuv_file_len - yuv_file_pos < VIDEO_WIDTH*VIDEO_HEIGHT*1.5)
- {
- fseek(yuv_file, 0, SEEK_SET);
- }
- }
- Render(hwnd);
- ReleaseDC(hwnd,hdc);
- break;
-
- case WM_PAINT:
- hdc = BeginPaint(hwnd, &ps);
- Render(hwnd);
- EndPaint(hwnd, &ps);
- break;
-
- case WM_CLOSE:
- fclose(yuv_file);
- KillTimer(hwnd,0);
- DestroyWindow(hwnd);
- break;
-
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
-
- default:
- return DefWindowProc(hwnd, msg, wParam, lParam);
- }
-
- return 0;
- }
-
-
-
-
- void RegisterMyWindow(HINSTANCE hInstance)
- {
- WNDCLASSEX wc;
-
- wc.cbSize = sizeof(WNDCLASSEX);
- wc.style = 0;
- wc.lpfnWndProc = MyWindowProc;
- wc.cbClsExtra = 0;
- wc.cbWndExtra = 0;
- wc.hInstance = hInstance;
- wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
- wc.hCursor = LoadCursor(NULL, IDC_ARROW);
- wc.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);
- wc.lpszMenuName = NULL;
- wc.lpszClassName= g_szClassName;
- wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
-
- if(!RegisterClassEx(&wc))
- {
- MessageBox(NULL, TEXT("窗口注册失败!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);
- exit(0);
- }
- }
-
-
-
-
- HWND CreateMyWindow(HINSTANCE hInstance, int nCmdShow)
- {
- HWND hwnd;
-
- RECT wr = {0,0,VIDEO_WIDTH,VIDEO_HEIGHT};
- AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
-
- hwnd = CreateWindowEx(
- WS_EX_CLIENTEDGE,
- g_szClassName,
- TEXT("My YUV Viewer"),
- WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, CW_USEDEFAULT,
- wr.right - wr.left,
- wr.bottom - wr.top,
-
- NULL, NULL, hInstance, NULL);
-
- if(hwnd == NULL)
- {
- MessageBox(NULL, TEXT("窗口创建失败!"),TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
- exit(0);
- }
-
- ShowWindow(hwnd, nCmdShow);
- UpdateWindow(hwnd);
-
- return hwnd;
- }
-
-
-
-
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
- LPSTR lpCmdLine, int nCmdShow)
- {
- HWND hwnd;
- MSG Msg;
-
- RegisterMyWindow(hInstance);
- hwnd = CreateMyWindow(hInstance, nCmdShow);
-
- while(GetMessage(&Msg, NULL, 0, 0) > 0)
- {
- TranslateMessage(&Msg);
- DispatchMessage(&Msg);
- }
-
- return Msg.wParam;
- }