最簡單的視音頻播放示例2:GDI播放YUV, RGB
前一篇文章對“Simplest Media Play”工程作了概括性介紹。后續幾篇文章打算詳細介紹每個子工程中的幾種技術。在記錄Direct3D,OpenGL這兩種相對復雜的技術之前,打算先記錄一種和它們屬于同一層面的的簡單的技術——GDI作為熱身。
GDI簡介
下面這段文字摘自維基百科:
圖形設備接口(Graphics Device Interface或Graphical Device Interface,縮寫GDI),是微軟公司視窗操作系統(Microsoft Windows)的三大核心部件(另外兩個是kernel、user)之一。GDI是微軟視窗系統表征圖形對象及將其傳送給諸如顯示器、打印機之類輸出設備的標準。其他系統也有類似GDI的東西,比如Macintosh的Quartz(傳統的QuickDraw),和GTK的GDK/Xlib。
GDI用來完成一些和繪制有關的工作,像直線或曲線的繪制,文字渲染,調色板控制。它最大的好處是它有可以直接訪問硬件設備的能力。通過GDI可以非常容易的在不同類型的設備上繪制圖形,像顯示屏和打印機或類似的顯示設備。這些能力是微軟Windows系統“所見即所得”程序的核心。
簡單的不需要快速圖形渲染的游戲可能會用到GDI。但是GDI對一些高級的動畫制作無能為力,它缺少顯卡中幀的概念,還缺少3D圖形硬件光柵化的支持等等。現代的游戲通常使用DirectX和OpenGL而不是GDI,因為這些技術能更好的讓程序員利用硬件的特性來加速圖形圖像的顯示。
下面這張圖可以很好的表現GDI(以及Direct3D)在系統中的位置。可以看出它們位于應用程序和硬件之間,和Direct3D屬于同一類型的東西。
我自己在之前做的碼流分析程序《VideoEye》中的“單幀詳細分析”模塊中曾經大量使用了GDI,因為那個功能需要在窗口中繪制幀圖像,量化參數,宏塊類型,運動矢量等參數。因此對GDI這部分的函數還算熟悉。例如下圖是當時畫出的“量化參數”,“宏塊劃分”和“運動矢量”分析結果。圖中的背景圖像,數字,直線都是通過GDI畫上去的。
視頻像素數據
視頻顯示的輸入數據一般情況下是非壓縮的RGB/YUV數據。像H.264這些壓縮碼流是不能用于顯示的。非壓縮的RGB/YUV數據在我們電腦里并不常見,因為它的體積實在是太大了。幾秒鐘的RGB/YUV數據就有幾十MB甚至上百MB大。舉個例子,5秒分辨率為1280x720,格式為RGB24的視頻數據體積(按照每秒25幀計算)為:
1280*720*3*25*5=345600000B=345.6MB
我們日常生活中比較常見的存儲非壓縮的RGB像素數據的格式就是BMP。它的文件體(除去文件頭)中存儲的是RGB數據。很容易發現,BMP文件明顯大于JPEG等壓縮格式的文件。
本文記錄的例子的使用的是純像素數據,和BMP這種封裝過的RGB數據最大的不同在于沒有文件頭,需要使用特定的播放器,設定參數之后才能正確播放。
PS:純像素數據播放器推薦以下兩個:
YUV Player Deluxe: 只能播放YUV格式,但是確實很好使。
Vooya: 除了支持YUV之外,還支持各種各樣的RGB數據,更加強大。
視頻顯示的要點
用GDI顯示像素數據是極其簡單的,難度遠遠低于Direct3D和OpenGL。可以分成兩步:
1. 構造一張BMP。
(1) 構造文件頭
(2) 讀取像素數據
2. 調用函數畫上去。
下面分步說明:
1. 構造一張BMP
(1) 構造文件頭
構造BMP需要用到結構體BITMAPINFO,該結構體主要用于存儲BMP文件頭信息:
[cpp] view plaincopy
- //BMP Header
- BITMAPINFO m_bmphdr={0};
- DWORD dwBmpHdr = sizeof(BITMAPINFO);
- m_bmphdr.bmiHeader.biBitCount = 24;
- m_bmphdr.bmiHeader.biClrImportant = 0;
- m_bmphdr.bmiHeader.biSize = dwBmpHdr;
- m_bmphdr.bmiHeader.biSizeImage = 0;
- m_bmphdr.bmiHeader.biWidth = pixel_w;
- //注意BMP在y方向是反著存儲的,一次必須設置一個負值,才能使圖像正著顯示出來
- m_bmphdr.bmiHeader.biHeight = -pixel_h;
- m_bmphdr.bmiHeader.biXPelsPerMeter = 0;
- m_bmphdr.bmiHeader.biYPelsPerMeter = 0;
- m_bmphdr.bmiHeader.biClrUsed = 0;
- m_bmphdr.bmiHeader.biPlanes = 1;
- m_bmphdr.bmiHeader.biCompression = BI_RGB;
從構造BMP這一步我們可以得知:像素格式必須轉換為RGB(即不能是YUV)才能使用GDI畫上去。因為BMP存儲像素是RGB格式的。
(2) 讀取像素數據
大端和小端
讀取像素數據的時候,又涉及到一個知識點:大端模式和小端模式。
假使我們直接讀取rgb24格式的一幀數據(在硬盤中的存儲方式為R1|G1|B1,R2|G2|B2,R3|G3|B3),然后顯示出來,會發現所有的人物都變成了“阿凡達”(忽然感覺這個比喻還挺形象的):就是人的皮膚都變成了藍色了。導致這個的原因實際上是系統把“R”當成“B”,而把“B”當成“R”的結果。因此,如果我們如果把“R”和“B”的順序調換一下的話(“G”保持不變),顯示就正常了。
針對上述現象,我們可以得知BMP的RGB24格式的數據實際上是B1|G1|R1,B2|G2|R2這樣的順序存儲的。那么問題來了,為什么會這么存儲呢?這么存儲的話,豈不是把像素格式起名字叫“BGR24”更合適?下面就詳細分析一下這個問題。
首先來看一下“RGB24”這種名稱的起名規則。它們是按照“高字節->低字節”的方式起名的。即高字節存“R”,低字節存“B”。在系統中,一個像素點的像素值(RGB24則是包含R,G,B三個8Byte的數據;RGBA則包含R,G,B,A四個8Byte的數據)被認為是一個“顏色”變量(官方有正規的結構體定義:tagRGBTRIPLE,tagRGBQUAD。也是類似于int,char這樣的變量)。這種長度超過1Byte的變量(int的長度也超過了1Byte)存儲的時候就涉及到一個的問題:變量的低字節應該保存在內存的高地址還是低地址?
對于上述問題,有兩種存儲方法:
大端模式(Big Endian),是指數據的低字節(注意是整個字節)保存在內存的高地址中,而數據的高字節,保存在內存的低地址中。其實這是一個更符合人類習慣的存儲方式。
小端模式(Little Endian),是指數據的低字節保存在內存的低地址中,而數據的高字節保存在內存的高地址中。
大部分用戶的操作系統(如Windows,FreeBsd,Linux)是Little Endian的。少部分,如MAC OS是Big Endian 的。此外,網絡字節序是Big Endian的。BMP文件的存儲規定是Little Endian。因此,例如對于3Byte的RGB24變量,其低字節為“B”,而低字節應該保存在內存的低地址中,因此應該保存在最“前面”。所以RGB24格式的像素數據,在BMP文件中的存儲方式是B1|G1|R1,B2|G2|R2…
也可以看一下官方的定義:
[cpp] view plaincopy
- typedef struct tagRGBTRIPLE {
- BYTE rgbtBlue; // 藍色分量
- BYTE rgbtGreen; // 綠色分量
- BYTE rgbtRed; // 紅色分量
- } RGBTRIPLE;
- typedef struct tagRGBQUAD {
- BYTE rgbBlue; // 藍色分量
- BYTE rgbGreen; // 綠色分量
- BYTE rgbRed; // 紅色分量
- BYTE rgbReserved; // 保留字節
- } RGBQUAD。
大端和小端的轉換
大端和小端的轉換其實非常簡單,只要交換它們的字節就可以了。下面代碼列出了24bit(3字節)的數據的大端和小端之間的轉換方法。
[cpp] view plaincopy
- //change endian of a pixel (24bit)
- void CHANGE_ENDIAN_24(unsigned char *data){
- char temp2=data[2];
- data[2]=data[0];
- data[0]=temp2;
- }
下面代碼列出了32bit(4字節)的數據的大端和小端之間轉換的方法。
[cpp] view plaincopy

- //change endian of a pixel (32bit)
- void CHANGE_ENDIAN_32(unsigned char *data){
- char temp3,temp2;
- temp3=data[3];
- temp2=data[2];
- data[3]=data[0];
- data[2]=data[1];
- data[0]=temp3;
- data[1]=temp2;
- }
通過調用上述兩個函數,可以實現整張RGB(24bit或者32bit)圖片的大端與小端之間的轉換。
[cpp] view plaincopy

- //Change endian of a picture
- void CHANGE_ENDIAN_PIC(unsigned char *image,int w,int h,int bpp){
- unsigned char *pixeldata=NULL;
- for(int i =0;i<h;i++)
- for(int j=0;j<w;j++){
- pixeldata=image+(i*w+j)*bpp/8;
- if(bpp==32){
- CHANGE_ENDIAN_32(pixeldata);
- }else if(bpp==24){
- CHANGE_ENDIAN_24(pixeldata);
- }
- }
- }
綜上所述,需要把輸入的rgb24格式的數據(在硬盤中的存儲方式為R1|G1|B1,R2|G2|B2,R3|G3|B3),經過大端和小端的轉換,然后調用顯示的函數,才能正確的顯示出來。
PS:當然,如果輸入是bgr24格式的數據(在硬盤中的存儲方式為B1|G1|R1,B2|G2|R2,B3|G3|R3)的話,是可以不用轉換直接正確的顯示的。但是要了解到并不是BMP硬性規定了B|G|R這樣的存儲順序。其中是有原因的。
顯示YUV420P數據
如果輸入像素格式是YUV420P的話,需要使用函數CONVERT_YUV420PtoRGB24()先將YUV420P格式數據轉換為rgb24的數據。需要注意的是轉換完的數據同樣要把“大端”轉換成“小端”才能正確的在屏幕上顯示。CONVERT_YUV420PtoRGB24()代碼要稍微復雜些,如下所示。其中提供了兩套公式用于YUV420P向rgb24轉換,效果略微有些區別,可以自己改改試試。
[cpp] view plaincopy

- inline byte CONVERT_ADJUST(double tmp)
- {
- return (byte)((tmp >= 0 && tmp <= 255)?tmp:(tmp < 0 ? 0 : 255));
- }
- //YUV420P to RGB24
- void CONVERT_YUV420PtoRGB24(unsigned char* yuv_src,unsigned char* rgb_dst,int nWidth,int nHeight)
- {
- unsigned char *tmpbuf=(unsigned char *)malloc(nWidth*nHeight*3);
- unsigned char Y,U,V,R,G,B;
- unsigned char* y_planar,*u_planar,*v_planar;
- int rgb_width , u_width;
- rgb_width = nWidth * 3;
- u_width = (nWidth >> 1);
- int ypSize = nWidth * nHeight;
- int upSize = (ypSize>>2);
- int offSet = 0;
- y_planar = yuv_src;
- u_planar = yuv_src + ypSize;
- v_planar = u_planar + upSize;
- for(int i = 0; i < nHeight; i++)
- {
- for(int j = 0; j < nWidth; j ++)
- {
- // Get the Y value from the y planar
- Y = *(y_planar + nWidth * i + j);
- // Get the V value from the u planar
- offSet = (i>>1) * (u_width) + (j>>1);
- V = *(u_planar + offSet);
- // Get the U value from the v planar
- U = *(v_planar + offSet);
- // Cacular the R,G,B values
- // Method 1
- R = CONVERT_ADJUST((Y + (1.4075 * (V - 128))));
- G = CONVERT_ADJUST((Y - (0.3455 * (U - 128) - 0.7169 * (V - 128))));
- B = CONVERT_ADJUST((Y + (1.7790 * (U - 128))));
- /*
- // The following formulas are from MicroSoft' MSDN
- int C,D,E;
- // Method 2
- C = Y - 16;
- D = U - 128;
- E = V - 128;
- R = CONVERT_ADJUST(( 298 * C + 409 * E + 128) >> 8);
- G = CONVERT_ADJUST(( 298 * C - 100 * D - 208 * E + 128) >> 8);
- B = CONVERT_ADJUST(( 298 * C + 516 * D + 128) >> 8);
- R = ((R - 128) * .6 + 128 )>255?255:(R - 128) * .6 + 128;
- G = ((G - 128) * .6 + 128 )>255?255:(G - 128) * .6 + 128;
- B = ((B - 128) * .6 + 128 )>255?255:(B - 128) * .6 + 128;
- */
- offSet = rgb_width * i + j * 3;
- rgb_dst[offSet] = B;
- rgb_dst[offSet + 1] = G;
- rgb_dst[offSet + 2] = R;
- }
- }
- free(tmpbuf);
- }
2. 調用函數畫上去。
最關鍵的繪圖函數只有一個:StretchDIBits()。該函數將矩形區域內像素數據拷貝到指定的目標矩形中。如果目標矩形與源矩形大小不一樣,那么函數將會對顏色數據的行和列進行拉伸或壓縮,以與目標矩形匹配。
StretchDIBits()這個函數的參數實在是太多了一共12個。它的原型如下:
[cpp] view plaincopy

- int StretchDIBits(HDC hdc, int XDest , int YDest , int nDestWidth, int nDestHeight, int XSrc, int Ysrc, int nSrcWidth, int nSrcHeight, CONST VOID *lpBits, CONST BITMAPINFO * lpBitsInfo, UINT iUsage, DWORD dwRop);
它的參數的意義:
hdc:指向目標設備環境的句柄。
XDest:指定目標矩形左上角位置的X軸坐標,按邏輯單位來表示坐標。
YDest:指定目標矩形左上角的Y軸坐標,按邏輯單位表示坐標。
nDestWidth:指定目標矩形的寬度。
nDestHeight:指定目標矩形的高度。
XSrc:指定DIB中源矩形(左上角)的X軸坐標,坐標以像素點表示。
YSrc:指定DIB中源矩形(左上角)的Y軸坐標,坐標以像素點表示。
nSrcWidth:按像素點指定DIB中源矩形的寬度。
nSrcHeight:按像素點指定DIB中源矩形的高度。
lpBits:指向DIB位的指針,這些位的值按字節類型數組存儲,有關更多的信息,參考下面的備注一節。
lpBitsInfo:指向BITMAPINFO結構的指針,該結構包含有關DIB方面的信息。
iUsage:表示是否提供了BITMAPINFO結構中的成員bmiColors,如果提供了,那么該bmiColors是否包含了明確的RGB值或索引。參數iUsage必須取下列值,這些值的含義如下:
DIB_PAL_COLORS:表示該數組包含對源設備環境的邏輯調色板進行索引的16位索引值。
DIB_RGB_COLORS:表示該顏色表包含原義的RGB值。
dwRop:指定源像素點、目標設備環境的當前刷子和目標像素點是如何組合形成新的圖像。
返回值:如果函數執行成功,那么返回值是拷貝的掃描線數目,如果函數執行失敗,那么返回值是GDI_ERROR。
別看StretchDIBits()那個函數看似很復雜,實際上我們只需要指定下面4個信息:源矩形,目標矩形,BMP文件頭,BMP文件數據。參考代碼如下。
[cpp] view plaincopy
- //將RGB數據畫在控件上
- int nResult = StretchDIBits(hdc,
- 0,0,
- screen_w,screen_h,
- 0, 0,
- pixel_w, pixel_h,
- raw_buffer,
- &m_bmphdr,
- DIB_RGB_COLORS,
- SRCCOPY);
最后補充一句,在畫圖之前首先要獲取目標窗口的HDC(Handle of Device Context,設備上下文句柄)。在畫圖完成之后要釋放HDC。

- HDC hdc=GetDC(hwnd);
- //畫圖…
- ReleaseDC(hwnd,hdc);
其他要點
本程序使用的是Win32的API創建的窗口。但注意這個并不是MFC應用程序的窗口。MFC代碼量太大,并不適宜用來做教程。因此使用Win32的API創建窗口。程序的入口函數是WinMain(),其中調用了CreateWindow()創建了顯示視頻的窗口。此外,程序中的消息循環使用的是PeekMessage()而不是GetMessage()。GetMessage()獲取消息后,將消息從系統中移除,當系統無消息時,會等待下一條消息,是阻塞函數。而函數PeekMesssge()是以查看的方式從系統中獲取消息,可以不將消息從系統中移除(相當于“偷看”消息),是非阻塞函數;當系統無消息時,返回FALSE,繼續執行后續代碼。使用PeekMessage()的好處是可以保證每隔40ms可以顯示下一幀畫面。
源代碼
下面貼上GDI顯示YUV/RGB的完整源代碼
[cpp] view plaincopy
- /**
- * 最簡單的GDI播放視頻的例子(GDI播放RGB/YUV)
- * Simplest Video Play GDI (GDI play RGB/YUV)
- *
- * 雷霄驊 Lei Xiaohua
- * leixiaohua1020@126.com
- * 中國傳媒大學/數字電視技術
- * Communication University of China / Digital TV Technology
- * http://blog.csdn.net/leixiaohua1020
- *
- * 本程序使用GDI播放RGB/YUV視頻像素數據。GDI實際上只能直接播放RGB數據。
- * 因此如果輸入數據為YUV420P的話,需要先轉換為RGB數據之后再進行播放。
- *
- * 函數調用步驟如下:
- * GetDC():獲得顯示設備的句柄。
- * 像素數據格式的轉換(如果需要的話)
- * 設置BMP文件頭...
- * StretchDIBits():指定BMP文件頭,以及像素數據,繪制。
- * ReleaseDC():釋放顯示設備的句柄。
- *
- * 在該示例程序中,包含了像素轉換的幾個工具函數,以及“大端”,
- * “小端”(字節順序)相互轉換的函數。
- *
- * This software plays RGB/YUV raw video data using GDI.
- * In fact GDI only can draw RGB data. So If input data is
- * YUV420P, it need to be convert to RGB first.
- * It's the simplest GDI tutorial (About video playback).
- *
- * The process is shown as follows:
- *
- * GetDC():retrieves a handle to a device context (DC).
- * Convert pixel data format(if needed).
- * Set BMP Header...
- * StretchDIBits():Set pixel data and BMP data and begin to draw.
- * ReleaseDC():release the handle.
- *
- * In this program there are some functions about conversion
- * between pixel format and conversion between "Big Endian" and
- * "Little Endian".
- */
- #include <stdio.h>
- #include <tchar.h>
- #include <Windows.h>
- //set '1' to choose a type of file to play
- #define LOAD_BGRA 0
- #define LOAD_RGB24 0
- #define LOAD_BGR24 0
- #define LOAD_YUV420P 1
- //Width, Height
- const int screen_w=500,screen_h=500;
- const int pixel_w=320,pixel_h=180;
- //Bit per Pixel
- #if LOAD_BGRA
- const int bpp=32;
- #elif LOAD_RGB24|LOAD_BGR24
- const int bpp=24;
- #elif LOAD_YUV420P
- const int bpp=12;
- #endif
- FILE *fp=NULL;
- //Storage frame data
- unsigned char buffer[pixel_w*pixel_h*bpp/8];
- unsigned char buffer_convert[pixel_w*pixel_h*3];
- //Not Efficient, Just an example
- //change endian of a pixel (32bit)
- void CHANGE_ENDIAN_32(unsigned char *data){
- char temp3,temp2;
- temp3=data[3];
- temp2=data[2];
- data[3]=data[0];
- data[2]=data[1];
- data[0]=temp3;
- data[1]=temp2;
- }
- //change endian of a pixel (24bit)
- void CHANGE_ENDIAN_24(unsigned char *data){
- char temp2=data[2];
- data[2]=data[0];
- data[0]=temp2;
- }
- //RGBA to RGB24 (or BGRA to BGR24)
- void CONVERT_RGBA32toRGB24(unsigned char *image,int w,int h){
- for(int i =0;i<h;i++)
- for(int j=0;j<w;j++){
- memcpy(image+(i*w+j)*3,image+(i*w+j)*4,3);
- }
- }
- //RGB24 to BGR24
- void CONVERT_RGB24toBGR24(unsigned char *image,int w,int h){
- for(int i =0;i<h;i++)
- for(int j=0;j<w;j++){
- char temp2;
- temp2=image[(i*w+j)*3+2];
- image[(i*w+j)*3+2]=image[(i*w+j)*3+0];
- image[(i*w+j)*3+0]=temp2;
- }
- }
- //Change endian of a picture
- void CHANGE_ENDIAN_PIC(unsigned char *image,int w,int h,int bpp){
- unsigned char *pixeldata=NULL;
- for(int i =0;i<h;i++)
- for(int j=0;j<w;j++){
- pixeldata=image+(i*w+j)*bpp/8;
- if(bpp==32){
- CHANGE_ENDIAN_32(pixeldata);
- }else if(bpp==24){
- CHANGE_ENDIAN_24(pixeldata);
- }
- }
- }
- inline unsigned char CONVERT_ADJUST(double tmp)
- {
- return (unsigned char)((tmp >= 0 && tmp <= 255)?tmp:(tmp < 0 ? 0 : 255));
- }
- //YUV420P to RGB24
- void CONVERT_YUV420PtoRGB24(unsigned char* yuv_src,unsigned char* rgb_dst,int nWidth,int nHeight)
- {
- unsigned char *tmpbuf=(unsigned char *)malloc(nWidth*nHeight*3);
- unsigned char Y,U,V,R,G,B;
- unsigned char* y_planar,*u_planar,*v_planar;
- int rgb_width , u_width;
- rgb_width = nWidth * 3;
- u_width = (nWidth >> 1);
- int ypSize = nWidth * nHeight;
- int upSize = (ypSize>>2);
- int offSet = 0;
- y_planar = yuv_src;
- u_planar = yuv_src + ypSize;
- v_planar = u_planar + upSize;
- for(int i = 0; i < nHeight; i++)
- {
- for(int j = 0; j < nWidth; j ++)
- {
- // Get the Y value from the y planar
- Y = *(y_planar + nWidth * i + j);
- // Get the V value from the u planar
- offSet = (i>>1) * (u_width) + (j>>1);
- V = *(u_planar + offSet);
- // Get the U value from the v planar
- U = *(v_planar + offSet);
- // Cacular the R,G,B values
- // Method 1
- R = CONVERT_ADJUST((Y + (1.4075 * (V - 128))));
- G = CONVERT_ADJUST((Y - (0.3455 * (U - 128) - 0.7169 * (V - 128))));
- B = CONVERT_ADJUST((Y + (1.7790 * (U - 128))));
- /*
- // The following formulas are from MicroSoft' MSDN
- int C,D,E;
- // Method 2
- C = Y - 16;
- D = U - 128;
- E = V - 128;
- R = CONVERT_ADJUST(( 298 * C + 409 * E + 128) >> 8);
- G = CONVERT_ADJUST(( 298 * C - 100 * D - 208 * E + 128) >> 8);
- B = CONVERT_ADJUST(( 298 * C + 516 * D + 128) >> 8);
- R = ((R - 128) * .6 + 128 )>255?255:(R - 128) * .6 + 128;
- G = ((G - 128) * .6 + 128 )>255?255:(G - 128) * .6 + 128;
- B = ((B - 128) * .6 + 128 )>255?255:(B - 128) * .6 + 128;
- */
- offSet = rgb_width * i + j * 3;
- rgb_dst[offSet] = B;
- rgb_dst[offSet + 1] = G;
- rgb_dst[offSet + 2] = R;
- }
- }
- free(tmpbuf);
- }
- bool Render(HWND hwnd)
- {
- //Read Pixel Data
- if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){
- // Loop
- fseek(fp, 0, SEEK_SET);
- fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp);
- }
- HDC hdc=GetDC(hwnd);
- //Note:
- //Big Endian or Small Endian?
- //ARGB order:high bit -> low bit.
- //ARGB Format Big Endian (low address save high MSB, here is A) in memory : A|R|G|B
- //ARGB Format Little Endian (low address save low MSB, here is B) in memory : B|G|R|A
- //Microsoft Windows is Little Endian
- //So we must change the order
- #if LOAD_BGRA
- CONVERT_RGBA32toRGB24(buffer,pixel_w,pixel_h);
- //we don't need to change endian
- //Because input BGR24 pixel data(B|G|R) is same as RGB in Little Endian (B|G|R)
- #elif LOAD_RGB24
- //Change to Little Endian
- CHANGE_ENDIAN_PIC(buffer,pixel_w,pixel_h,24);
- #elif LOAD_BGR24
- //In fact we don't need to do anything.
- //Because input BGR24 pixel data(B|G|R) is same as RGB in Little Endian (B|G|R)
- //CONVERT_RGB24toBGR24(buffer,pixel_w,pixel_h);
- //CHANGE_ENDIAN_PIC(buffer,pixel_w,pixel_h,24);
- #elif LOAD_YUV420P
- //YUV Need to Convert to RGB first
- //YUV420P to RGB24
- CONVERT_YUV420PtoRGB24(buffer,buffer_convert,pixel_w,pixel_h);
- //Change to Little Endian
- CHANGE_ENDIAN_PIC(buffer_convert,pixel_w,pixel_h,24);
- #endif
- //BMP Header
- BITMAPINFO m_bmphdr={0};
- DWORD dwBmpHdr = sizeof(BITMAPINFO);
- //24bit
- m_bmphdr.bmiHeader.biBitCount = 24;
- m_bmphdr.bmiHeader.biClrImportant = 0;
- m_bmphdr.bmiHeader.biSize = dwBmpHdr;
- m_bmphdr.bmiHeader.biSizeImage = 0;
- m_bmphdr.bmiHeader.biWidth = pixel_w;
- //Notice: BMP storage pixel data in opposite direction of Y-axis (from bottom to top).
- //So we must set reverse biHeight to show image correctly.
- m_bmphdr.bmiHeader.biHeight = -pixel_h;
- m_bmphdr.bmiHeader.biXPelsPerMeter = 0;
- m_bmphdr.bmiHeader.biYPelsPerMeter = 0;
- m_bmphdr.bmiHeader.biClrUsed = 0;
- m_bmphdr.bmiHeader.biPlanes = 1;
- m_bmphdr.bmiHeader.biCompression = BI_RGB;
- //Draw data
- #if LOAD_YUV420P
- //YUV420P data convert to another buffer
- int nResult = StretchDIBits(hdc,
- 0,0,
- screen_w,screen_h,
- 0, 0,
- pixel_w, pixel_h,
- buffer_convert,
- &m_bmphdr,
- DIB_RGB_COLORS,
- SRCCOPY);
- #else
- //Draw data
- int nResult = StretchDIBits(hdc,
- 0,0,
- screen_w,screen_h,
- 0, 0,
- pixel_w, pixel_h,
- buffer,
- &m_bmphdr,
- DIB_RGB_COLORS,
- SRCCOPY);
- #endif
- ReleaseDC(hwnd,hdc);
- return true;
- }
- LRESULT WINAPI MyWndProc(HWND hwnd, UINT msg, WPARAM wparma, LPARAM lparam)
- {
- switch(msg){
- case WM_DESTROY:
- PostQuitMessage(0);
- return 0;
- }
- return DefWindowProc(hwnd, msg, wparma, lparam);
- }
- int WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in LPSTR lpCmdLine, __in int nShowCmd )
- {
- WNDCLASSEX wc;
- ZeroMemory(&wc, sizeof(wc));
- wc.cbSize = sizeof(wc);
- wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
- wc.lpfnWndProc = (WNDPROC)MyWndProc;
- wc.lpszClassName = _T("GDI");
- wc.style = CS_HREDRAW | CS_VREDRAW;
- RegisterClassEx(&wc);
- HWND hwnd = NULL;
- hwnd = CreateWindow(_T("GDI"), _T("Simplest Video Play GDI"), WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hInstance, NULL);
- if (hwnd==NULL){
- return -1;
- }
- ShowWindow(hwnd, nShowCmd);
- UpdateWindow(hwnd);
- #if LOAD_BGRA
- fp=fopen("../test_bgra_320x180.rgb","rb+");
- #elif LOAD_RGB24
- fp=fopen("../test_rgb24_320x180.rgb","rb+");
- #elif LOAD_BGR24
- fp=fopen("../test_bgr24_320x180.rgb","rb+");
- #elif LOAD_YUV420P
- fp=fopen("../test_yuv420p_320x180.yuv","rb+");
- #endif
- if(fp==NULL){
- printf("Cannot open this file.\n");
- return -1;
- }
- MSG msg;
- ZeroMemory(&msg, sizeof(msg));
- while (msg.message != WM_QUIT){
- //PeekMessage() is not same as GetMessage
- if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- else{
- Sleep(40);
- Render(hwnd);
- }
- }
- UnregisterClass(_T("GDI"), hInstance);
- return 0;
- }
代碼注意事項
1.可以通過設置定義在文件開始出的宏,決定讀取哪個格式的像素數據(bgra,rgb24,bgr24,yuv420p)。
[cpp] view plaincopy

- //set '1' to choose a type of file to play
- #define LOAD_BGRA 0
- #define LOAD_RGB24 0
- #define LOAD_BGR24 0
- #define LOAD_YUV420P 1
2.窗口的寬高為screen_w,screen_h。像素數據的寬高為pixel_w,pixel_h。它們的定義如下。
[cpp] view plaincopy

- //Width, Height
- const int screen_w=500,screen_h=500;
- const int pixel_w=320,pixel_h=180;
程序流程圖
程序的流程圖可以簡單概括如下所示。
結果
不論選擇讀取哪個格式的文件,程序的最終輸出效果都是一樣的,如下圖所示。
下載
代碼位于“Simplest Media Play”中
SourceForge項目地址:https://sourceforge.net/projects/simplestmediaplay/
CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8054395
上述工程包含了使用各種API(Direct3D,OpenGL,GDI,DirectSound,SDL2)播放多媒體例子。其中音頻輸入為PCM采樣數據。輸出至系統的聲卡播放出來。視頻輸入為YUV/RGB像素數據。輸出至顯示器上的一個窗口播放出來。
通過本工程的代碼初學者可以快速學習使用這幾個API播放視頻和音頻的技術。
一共包括了如下幾個子工程:
simplest_audio_play_directsound: 使用DirectSound播放PCM音頻采樣數據。
simplest_audio_play_sdl2: 使用SDL2播放PCM音頻采樣數據。
simplest_video_play_direct3d: 使用Direct3D的Surface播放RGB/YUV視頻像素數據。
simplest_video_play_direct3d_texture: 使用Direct3D的Texture播放RGB視頻像素數據。
simplest_video_play_gdi: 使用GDI播放RGB/YUV視頻像素數據。
simplest_video_play_opengl: 使用OpenGL播放RGB/YUV視頻像素數據。
simplest_video_play_opengl_texture: 使用OpenGL的Texture播放YUV視頻像素數據。
simplest_video_play_sdl2: 使用SDL2播放RGB/YUV視頻像素數據。
from:http://blog.csdn.net/leixiaohua1020/article/details/40266503
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成