基于GDI的入门级YUV播放器设计


来源: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

程序中所采用的代码如下:

[cpp]  view plain  copy
  1. // ++++++++++++++++++++++++++++++++++++++++++++++++++++ //  
  2. // 色域转换公式(YUV->RGB)  
  3. // R = 1.164*(Y-16) + 1.596*(V-128)  
  4. // G = 1.164*(Y-16) - 0.813*(V-128) - 0.392*(U-128)  
  5. // B = 1.164*(Y-16) + 2.017*(U-128)  
  6. // ---------------------------------------------------- //  
  7. // R = (1192*(Y-16) + 1634*(V-128))/1024  
  8. // G = (1192*(Y-16) -  833*(V-128) - 401*(U-128))/1024  
  9. // B = (1192*(Y-16) + 2065*(U-128))/1024  
  10. // ++++++++++++++++++++++++++++++++++++++++++++++++++++ //  
  11. void yuv2rgb(BYTE y, BYTE u, BYTE v, BYTE *r, BYTE *g, BYTE *b)  
  12. {  
  13.     int y0,u0,v0;  
  14.     int m0,m1,m2,m3,m4;  
  15.     int r0,g0,b0;  
  16.       
  17.     y0 = y - 16;  
  18.     u0 = u - 128;  
  19.     v0 = v - 128;  
  20.   
  21.     m0 = 1192*y0;  
  22.     m1 = 1634*v0;  
  23.     m2 = 833 *v0;  
  24.     m3 = 401 *u0;  
  25.     m4 = 2065*u0;  
  26.   
  27.     r0 = (m0 + m1) >> 10;  
  28.     g0 = (m0 - m2 - m3) >> 10;  
  29.     b0 = (m0 + m4) >> 10;  
  30.   
  31.     if(r0 > 255)  
  32.         *r = 255;  
  33.     else if(r0 < 0)  
  34.         *r = 0;  
  35.     else  
  36.         *r = r0;  
  37.       
  38.     if(g0 > 255)  
  39.         *g = 255;  
  40.     else if(g0 < 0)  
  41.         *g = 0;  
  42.     else  
  43.         *g = g0;  
  44.       
  45.     if(b0 > 255)  
  46.         *b = 255;  
  47.     else if(b0 < 0)  
  48.         *b = 0;  
  49.     else  
  50.         *b = b0;  
  51. }  

渲染函数

程序采用了一个单独的渲染函数来完成绘图的工作,如下面的代码所示,渲染函数依次完成了动态内存分配,YUV图片读取、色域转换、BITMAPINFO添加以及位图显示。这里需要注意的是BITMAPINFO的biHeight需要设置成负数,因为我们的RGB数据是按照从上到下,从左到右的方式存储的。


消息处理函数

本文所采用的消息处理函数主要完成文件的操作、帧率的控制、以及画面的渲染。其中渲染函数在WM_PAINT和WM_TIMER中都被调用。大部分时间的显示由WM_TIMER控制,这样的好处是避免了WM_PAINT调用时的窗口清除,避免了画面的闪烁。


演示效果

下图为本程序的演示效果:



源代码

完整的源代码如下,用户如果想用这段代码,需要做一些前提准备和修改。用户需要准备一个UYVY或者I420文件,并根据文件的属性修改 YUV_FILE_NAME VIDEO_WIDTH VIDEO_HEIGHT YUV_TYPE
[cpp]  view plain  copy
  1. // +FHDR-------------------------------------------------------------------------------------  
  2. // 一个简单的基于GDI的YUV播放器  
  3. // 作者: Jason Chen  
  4. // 博客地址: http://blog.csdn.net/jiandan524  
  5. // 微信公众号: 视频门徒  
  6. // ------------------------------------------------------------------------------------------  
  7. // 该播放器通过WINAPI直接调用GDI进行显示,具体完成如下几个功能:  
  8. // <1> 根据分辨率自动生成相应大小的显示窗口  
  9. // <2> 支持UYVY、I420两种文件格式的播放  
  10. // <3> 完成YUV2RGB的色域转换  
  11. // <4> 利用Timer控制帧率  
  12. // -FHDR-------------------------------------------------------------------------------------  
  13. #include <Windows.h>  
  14. #include <math.h>  
  15. #include <stdio.h>  
  16. #include <stdlib.h>  
  17.   
  18. const char g_szClassName[] = "myYuvViewClass";  
  19.   
  20. // UYVY文件类型  
  21. #define T_UYVY    0  
  22. // U0 Y0 V1 Y1 U2 Y2 V3 Y3 U4 Y4 V5 Y5 U6 Y6 V7 Y7  
  23.   
  24. // I420文件格式  
  25. #define T_I420 1  
  26. // Y0  Y1  Y2  Y3  Y4  Y5  Y6  Y7  
  27. // Y8  Y9  Y10 Y11 Y12 Y13 Y14 Y15  
  28. // U0  U1  U2  U3  
  29. // V0  V1  V2  V3  
  30.   
  31. //#define YUV_FILE_NAME "monkeyking-tlr1_h720p_clip.yuv"  
  32. #define YUV_FILE_NAME "sunflower_720p.uyvy" // YUV文件名称  
  33. #define VIDEO_WIDTH   1280                  // 视频宽度  
  34. #define VIDEO_HEIGHT  720                   // 视频高度  
  35. #define YUV_TYPE      T_UYVY                // YUV文件类型  
  36.   
  37. HDC hdc;  
  38. PAINTSTRUCT ps;  
  39.   
  40. FILE  *yuv_file;  
  41. fpos_t yuv_file_len;  
  42. fpos_t yuv_file_pos;  
  43.   
  44. unsigned int iyuv_type;  
  45.   
  46. // ++++++++++++++++++++++++++++++++++++++++++++++++++++ //  
  47. // 色域转换公式(YUV->RGB)  
  48. // R = 1.164*(Y-16) + 1.596*(V-128)  
  49. // G = 1.164*(Y-16) - 0.813*(V-128) - 0.392*(U-128)  
  50. // B = 1.164*(Y-16) + 2.017*(U-128)  
  51. // ---------------------------------------------------- //  
  52. // R = (1192*(Y-16) + 1634*(V-128))/1024  
  53. // G = (1192*(Y-16) -  833*(V-128) - 401*(U-128))/1024  
  54. // B = (1192*(Y-16) + 2065*(U-128))/1024  
  55. // ++++++++++++++++++++++++++++++++++++++++++++++++++++ //  
  56. void yuv2rgb(BYTE y, BYTE u, BYTE v, BYTE *r, BYTE *g, BYTE *b)  
  57. {  
  58.     int y0,u0,v0;  
  59.     int m0,m1,m2,m3,m4;  
  60.     int r0,g0,b0;  
  61.       
  62.     y0 = y - 16;  
  63.     u0 = u - 128;  
  64.     v0 = v - 128;  
  65.   
  66.     m0 = 1192*y0;  
  67.     m1 = 1634*v0;  
  68.     m2 = 833 *v0;  
  69.     m3 = 401 *u0;  
  70.     m4 = 2065*u0;  
  71.   
  72.     r0 = (m0 + m1) >> 10;  
  73.     g0 = (m0 - m2 - m3) >> 10;  
  74.     b0 = (m0 + m4) >> 10;  
  75.   
  76.     if(r0 > 255)  
  77.         *r = 255;  
  78.     else if(r0 < 0)  
  79.         *r = 0;  
  80.     else  
  81.         *r = r0;  
  82.       
  83.     if(g0 > 255)  
  84.         *g = 255;  
  85.     else if(g0 < 0)  
  86.         *g = 0;  
  87.     else  
  88.         *g = g0;  
  89.       
  90.     if(b0 > 255)  
  91.         *b = 255;  
  92.     else if(b0 < 0)  
  93.         *b = 0;  
  94.     else  
  95.         *b = b0;  
  96. }  
  97.   
  98. /* 
  99.  * 渲染函数 
  100.  */  
  101. void Render(HWND hwnd)  
  102. {  
  103.     int i,j;  
  104.     //BYTE *y,*u,*v;  
  105.     BYTE *yuv;  
  106.     BYTE *rgb;  
  107.   
  108.     BYTE y,u,v;  
  109.     BYTE r,g,b;  
  110.   
  111.     BITMAPINFO       dibInfo;  
  112.   
  113.     // RGB动态分配内存  
  114.     rgb = (BYTE *)malloc(sizeof(BYTE)*VIDEO_WIDTH*VIDEO_HEIGHT*3);  
  115.       
  116.     // YUV动态分配内存,根据文件格式不同而不同  
  117.     if(iyuv_type == T_UYVY)  
  118.     {  
  119.         yuv = (BYTE *)malloc(sizeof(BYTE)*VIDEO_WIDTH*VIDEO_HEIGHT*2);  
  120.         fread(yuv,sizeof(BYTE),VIDEO_WIDTH*VIDEO_HEIGHT*2,yuv_file);  
  121.     }  
  122.   
  123.     if(iyuv_type == T_I420)  
  124.     {  
  125.         yuv = (BYTE *)malloc(sizeof(BYTE)*VIDEO_WIDTH*VIDEO_HEIGHT*1.5);  
  126.         fread(yuv,sizeof(BYTE),VIDEO_WIDTH*VIDEO_HEIGHT*1.5,yuv_file);  
  127.     }  
  128.       
  129.     // 执行色域转换(YUV2RGB)  
  130.     for(i=0; i<VIDEO_HEIGHT; i++)  
  131.         for(j=0; j<VIDEO_WIDTH; j++)  
  132.         {  
  133.             if(iyuv_type == T_UYVY)  
  134.             {  
  135.                 y = yuv[i*VIDEO_WIDTH*2 + j*2 + 1];  
  136.                 u = yuv[i*VIDEO_WIDTH*2 + (j/2)*4];  
  137.                 v = yuv[i*VIDEO_WIDTH*2 + (j/2)*4 + 2];  
  138.             }  
  139.   
  140.             if(iyuv_type == T_I420)  
  141.             {  
  142.                 y = yuv[i*VIDEO_WIDTH + j];  
  143.                 u = yuv[VIDEO_WIDTH*VIDEO_HEIGHT + (i>>1)*VIDEO_WIDTH/2 + (j>>1)];  
  144.                 v = yuv[VIDEO_WIDTH*VIDEO_HEIGHT*5/4 + (i>>1)*VIDEO_WIDTH/2 + (j>>1)];  
  145.             }  
  146.                 yuv2rgb(y,u,v,&r,&g,&b);  
  147.   
  148.                 rgb[i*VIDEO_WIDTH*3+j*3+2] = r;  
  149.                 rgb[i*VIDEO_WIDTH*3+j*3+1] = g;  
  150.                 rgb[i*VIDEO_WIDTH*3+j*3]   = b;  
  151.         }  
  152.   
  153.     // 组装位图信息头  
  154.     dibInfo.bmiHeader.biSize            = sizeof(BITMAPINFO);  
  155.     dibInfo.bmiHeader.biWidth           = VIDEO_WIDTH;  
  156.     dibInfo.bmiHeader.biHeight          = -VIDEO_HEIGHT;  
  157.     dibInfo.bmiHeader.biPlanes          = 1;  
  158.     dibInfo.bmiHeader.biBitCount        = 24;  
  159.     dibInfo.bmiHeader.biCompression     = 0;  
  160.     dibInfo.bmiHeader.biSizeImage       = VIDEO_WIDTH*VIDEO_HEIGHT*3;  
  161.     dibInfo.bmiHeader.biXPelsPerMeter   = 0x0ec4;  
  162.     dibInfo.bmiHeader.biYPelsPerMeter   = 0x0ec4;  
  163.     dibInfo.bmiHeader.biClrUsed         = 0;  
  164.     dibInfo.bmiHeader.biClrImportant    = 0;  
  165.   
  166.     int nResult = StretchDIBits(  
  167.         hdc,  
  168.         0, 0,  
  169.         VIDEO_WIDTH, VIDEO_HEIGHT,  
  170.         0, 0,  
  171.         VIDEO_WIDTH, VIDEO_HEIGHT,  
  172.         rgb,  
  173.         &dibInfo,  
  174.         DIB_RGB_COLORS,  
  175.         SRCCOPY);  
  176.       
  177.     // 释放内存  
  178.     free(rgb);    
  179.     free(yuv);  
  180. }  
  181.   
  182. /* 
  183.  * 消息处理函数 
  184.  */  
  185. LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)  
  186. {  
  187.     switch(msg)  
  188.     {  
  189.         case WM_CREATE:  
  190.             // 创建窗口是打开YUV文件和定时器  
  191.             iyuv_type = YUV_TYPE;  
  192.             if((yuv_file=fopen(YUV_FILE_NAME,"rb")) == NULL)  
  193.                 {  
  194.                     MessageBox(NULL, TEXT("YUV文件打开失败!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);  
  195.                     exit(0);  
  196.                 }  
  197.   
  198.             fseek(yuv_file, 0, SEEK_END);  
  199.             fgetpos(yuv_file,&yuv_file_len);  
  200.             fseek(yuv_file, 0, SEEK_SET);  
  201.   
  202.             if(iyuv_type == T_UYVY)  
  203.             {  
  204.                 if(yuv_file_len < VIDEO_WIDTH*VIDEO_HEIGHT*2)  
  205.                 {  
  206.                     MessageBox(NULL,TEXT("YUV数据不足一帧!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);  
  207.                     exit(0);  
  208.                 }  
  209.             }  
  210.   
  211.             if(iyuv_type == T_I420)  
  212.             {  
  213.                 if(yuv_file_len < VIDEO_WIDTH*VIDEO_HEIGHT*1.5)  
  214.                 {  
  215.                     MessageBox(NULL,TEXT("YUV数据不足一帧!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);  
  216.                     exit(0);  
  217.                 }  
  218.             }  
  219.   
  220.             // 设定定时器,每30ms更新一次显示区域  
  221.             SetTimer(hwnd,0,30,0);  
  222.             break;  
  223.   
  224.         // 更新显示区域已经循环控制  
  225.         case WM_TIMER:  
  226.             //InvalidateRect(hwnd,0,TRUE);  
  227.             hdc = GetDC(hwnd);  
  228.             fgetpos(yuv_file,&yuv_file_pos);  
  229.               
  230.             if(iyuv_type == T_UYVY)  
  231.             {  
  232.                 if(yuv_file_len - yuv_file_pos < VIDEO_WIDTH*VIDEO_HEIGHT*2)  
  233.                 {  
  234.                     fseek(yuv_file, 0, SEEK_SET);  
  235.                 }  
  236.             }  
  237.   
  238.             if(iyuv_type == T_I420)  
  239.             {  
  240.                 if(yuv_file_len - yuv_file_pos < VIDEO_WIDTH*VIDEO_HEIGHT*1.5)  
  241.                 {  
  242.                     fseek(yuv_file, 0, SEEK_SET);  
  243.                 }  
  244.             }  
  245.             Render(hwnd);  
  246.             ReleaseDC(hwnd,hdc);  
  247.             break;  
  248.   
  249.         case WM_PAINT:  
  250.             hdc = BeginPaint(hwnd, &ps);  
  251.             Render(hwnd);  
  252.             EndPaint(hwnd, &ps);  
  253.             break;  
  254.   
  255.         case WM_CLOSE:  
  256.             fclose(yuv_file);  
  257.             KillTimer(hwnd,0);  
  258.             DestroyWindow(hwnd);  
  259.             break;  
  260.   
  261.         case WM_DESTROY:  
  262.             PostQuitMessage(0);  
  263.             break;  
  264.   
  265.         default:  
  266.             return DefWindowProc(hwnd, msg, wParam, lParam);  
  267.     }  
  268.   
  269.     return 0;  
  270. }  
  271.   
  272. /* 
  273.  * 窗口注册函数 
  274.  */  
  275. void RegisterMyWindow(HINSTANCE hInstance)  
  276. {  
  277.     WNDCLASSEX wc;  
  278.   
  279.     wc.cbSize       = sizeof(WNDCLASSEX);  
  280.     wc.style        = 0;  
  281.     wc.lpfnWndProc  = MyWindowProc;  
  282.     wc.cbClsExtra   = 0;  
  283.     wc.cbWndExtra   = 0;  
  284.     wc.hInstance    = hInstance;  
  285.     wc.hIcon        = LoadIcon(NULL,IDI_APPLICATION);  
  286.     wc.hCursor      = LoadCursor(NULL, IDC_ARROW);  
  287.     wc.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);  
  288.     wc.lpszMenuName = NULL;  
  289.     wc.lpszClassName= g_szClassName;  
  290.     wc.hIconSm      = LoadIcon(NULL, IDI_APPLICATION);  
  291.   
  292.     if(!RegisterClassEx(&wc))  
  293.     {  
  294.         MessageBox(NULL, TEXT("窗口注册失败!"),TEXT("错误"),MB_ICONEXCLAMATION | MB_OK);  
  295.         exit(0);  
  296.     }  
  297. }  
  298.   
  299. /* 
  300.  * 窗口创建函数 
  301.  */  
  302. HWND CreateMyWindow(HINSTANCE hInstance, int nCmdShow)  
  303. {  
  304.     HWND hwnd;  
  305.     // 调整工作区的大小,用于显示完整的图片  
  306.     RECT wr = {0,0,VIDEO_WIDTH,VIDEO_HEIGHT};  
  307.     AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);  
  308.   
  309.     hwnd = CreateWindowEx(  
  310.         WS_EX_CLIENTEDGE,  
  311.         g_szClassName,  
  312.         TEXT("My YUV Viewer"),  
  313.         WS_OVERLAPPEDWINDOW,  
  314.         CW_USEDEFAULT, CW_USEDEFAULT,   
  315.         wr.right - wr.left,  
  316.         wr.bottom - wr.top,  
  317.         //VIDEO_WIDTH, VIDEO_HEIGHT,  
  318.         NULL, NULL, hInstance, NULL);  
  319.   
  320.     if(hwnd == NULL)  
  321.     {  
  322.         MessageBox(NULL, TEXT("窗口创建失败!"),TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);  
  323.         exit(0);  
  324.     }  
  325.   
  326.     ShowWindow(hwnd, nCmdShow);  
  327.     UpdateWindow(hwnd);  
  328.   
  329.     return hwnd;  
  330. }  
  331.   
  332. /* 
  333.  * 主函数 
  334.  */  
  335. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,  
  336.     LPSTR lpCmdLine, int nCmdShow)  
  337. {  
  338.     HWND hwnd;  
  339.     MSG  Msg;  
  340.   
  341.     RegisterMyWindow(hInstance);  
  342.     hwnd = CreateMyWindow(hInstance, nCmdShow);  
  343.   
  344.     while(GetMessage(&Msg, NULL, 0, 0) > 0)  
  345.     {  
  346.         TranslateMessage(&Msg);  
  347.         DispatchMessage(&Msg);  
  348.     }  
  349.   
  350.     return Msg.wParam;  
  351. }  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值