使用windows API函數讀取AVI視頻文件

下面的代碼以只讀方式打開AVI文件。szFile是打開文件的名字。title[100]用來修改window標題(顯示AVI文件信息).。 

首先調用AVIFileInit()。初始化AVI文件庫(使東西能用)。

打開AVI文件有很多方法.我採用AVIStreamOpenFromFile(...).他能打開AVI文件中單獨一個流(AVI文件可以包含多個流).它的參數如下:pavi是接收流句柄的緩衝的指針,szFile是打開文件的名字(包括路徑).第三參數是打開的流的類型.在這個工程裏,我們只對視頻流感興趣(streamtypeVIDEO).第四參數是0,這表示我們需要第一次讀到的視頻流(一個AVI文件裏會有多個視頻流,我們要第一個).OF_READ表示以只讀方式打開文件.最後一個參數是一個類標識句柄的指針.說實話,我也不清楚他是幹嗎的.我讓windows自己設定,於是把NULL傳過去.

void  OpenAVI(LPCSTR   szFile)//   打開AVI文件szFile

{

TCHAR title[100];//   包含修改了的window標題

AVIFileInit();//   打開AVI文件庫

//   打開AVI流

if  (AVIStreamOpenFromFile(&pavi,  szFile,   streamtypeVIDEO,   0,  OF_READ,   NULL)   !=0)

{

//   打開流時的出錯處理

MessageBox  (HWND_DESKTOP,   "打開AVI流失敗",   "錯誤",   MB_OK   |  MB_ICONEXCLAMATION);

}

  到目前爲止,我們假定文件被正確打開,流被正確定位!然後用AVIStreamInfo(...)從AVI文件裏抓取一些信息.  

先前我們創建了叫psi的結構體來保存AVI流的信息.下面第一行,我們把AVI信息填入該結構體.從流的寬度(以像素計)到動畫的幀速等所有的信息都會存到psi中.那些想要精確控制播放速度的要記住我剛纔說的.更多的信息參閱MSDN.

 

我們通過右邊位置減左邊位置算出幀寬.這個結果是以像素記的精確的幀寬.至於高度,可以用底邊位置減頂邊位置得到.這樣得到高度的像素值.

 

然後用AVIStreamLength(...)得到AVI文件最後一幀的序號.AVIStreamLength(...)返回動畫最後一幀的序號.結果存在lastframe裏.

 

計算幀速很簡單.每秒幀速(fps)=  psi.dwRate/psi,dwScale.返回的值應該匹配顯示幀的速度(你在AVI動畫中右擊鼠標可以看到).你會問那麼這和mpf有什麼關係呢?第一次寫這個代碼時,我試着用fps來選擇動畫了正確的幀面.我遇到一個問題...視頻放的太快!於是我看了一下視頻屬性.face2.avi文件有3.36秒長.幀速是29.974fps.視頻動畫共有91幀.而3.36*29.974   =   100.71.非常奇怪!!

 

所以我採用一些不同的方法.不是計算幀速,我計算每一幀播放所需時間.AVIStreamSampleToTime()把在動畫中的位置轉換位你到達該位置所需的時間(毫秒計).所以通過計算到達最後一幀的時間就得到整個動畫的播放時間.再拿這個結果除以動畫總幀數(lastframe).這樣就給出了每幀的顯示時間(毫秒計).結果存在mpf(milliseconds   per   frame)裏.你也能通過獲取動畫中一幀的時間來算每幀的毫秒數,代碼爲:AVIStreamSampleToTime(pavi,1).兩種方法都不錯!非常感謝Albert   Chaulk提供思路!

我說每幀的毫秒數不精確是因爲mpf是一個整型值,所以所有的浮點數都會被取整.

AVIStreamInfo(pavi,   &psi,  sizeof(psi));//   把流信息讀進psi

width=psi.rcFrame.right-psi.rcFrame.left;//   寬度爲右邊減左邊

height=psi.rcFrame.bottom-psi.rcFrame.top;//   高爲底邊減頂邊

lastframe=AVIStreamLength(pavi);//   最後一幀的序號

mpf=AVIStreamSampleToTime(pavi,lastframe)/lastframe;//   mpf的不精確值

  我們可利用Windows   Dib函數去做.  

首先要做的是描述我們想要的圖像的類型.於是我們要以所需參數填好bmih這個BitmapInfoHeader結構.

首先設定該結構體的大小.再把位平面數設爲1.3字節的數據有24比特(RGB).要使圖像位256像素寬,256像素高,最後要讓數據返回爲UNCOMPRESSED(非壓縮)的RGB數據(BI_RGB).  

 

CreateDIBSection創建一個可直接寫的設備無關位圖(dib).如果一切順利,hBitmap會指向該dib的比特值.hdc是設備上下文(DC)的句柄第二參數是BitmapInfo結構體的指針.該結構體包含了上述dib文件的信息.第三參數(DIB_RGB_COLORS)設定數據是RGB值.data是指向DIB比特值位置的指針的指針(嗚,真繞口).第五參數設爲NULL,我們的DIB已被分配好內存.末了,最後一個參數可忽略(設爲NULL).

 

引自MSDN:SelecObject函數選一個對象進入設備上下文(DC).

 

現在我們建好一個能直接寫的DIB,yeah:)

bmih.biSize=   sizeof  (BITMAPINFOHEADER);//  BitmapInfoHeader的大小

bmih.biPlanes=   1;//  位平面

bmih.biBitCount=   24;//比特格式(24   Bit,  3   Bytes)

bmih.biWidth=   256; //  寬度(256   Pixels)

bmih.biHeight=   256;//  高度   (256  Pixels)

bmih.biCompression =   BI_RGB; //  申請的模式   =   RGB

hBitmap  =   CreateDIBSection   (hdc,  (BITMAPINFO*)(&bmih),  DIB_RGB_COLORS,  (void**)(&data),   NULL,   NULL);

SelectObject   (hdc,  hBitmap);//   選hBitmap進入設備上下文(hdc)

      

  在從AVI中讀取幀面前還有幾件事要做.接下來使程序做好從AVI文件中解出幀面的準備.用AVIStreamGetFrameOpen(...)函數做這一點.  

你能給這個函數傳一個結構體作爲第二參數(它會返回一個特定的視頻格式).糟糕的是,你能改變的唯一數據是返回的圖像的寬度和高度.MSDN也提到能傳AVIGETFRAMEF_BESTDISPLAYFMT爲參數來選擇一個最佳顯示格式.奇怪的是,我的編譯器沒有定義這玩藝兒.

 

如果一切順利,一個GETFRAME對象被返回(用來讀幀數據).有問題的話,提示框會出現在屏幕上告訴你有錯誤!

pgf=AVIStreamGetFrameOpen(pavi,   NULL); //  用要求的模式建PGETFRAME

if  (pgf==NULL)

{

//   解幀出錯

MessageBox  (HWND_DESKTOP,   "不能打開AVI幀",   "錯誤",   MB_OK   |  MB_ICONEXCLAMATION);

}

  下面的代碼把視頻寬,高和幀數傳給window標題.用函數SetWindowText(...)在window頂部顯示標題.以窗口模式運行程序看看以下代碼的作用.  

//  bt標題欄信息(寬   /   高/   幀數)

wsprintf  (title,   "NeHe 's   AVI  Player:   Width:   %d,  Height:   %d,   Frames:  %d ",   width,   height,  lastframe);

SetWindowText(g_window-> hWnd,   title);//  修改標題欄

}

下面是有趣的東西...從AVI中抓取一幀,把它轉爲大小和色深可用的圖象.lpbi包含一幀的BitmapInfoHeader信息.我們在下面第二行完成了幾件事.先是抓了動畫的一幀...我們需要的幀面由這些幀確定.這會讓動畫走掉這一幀,lpbi會指向這一幀的頭信息.  

下面是有趣的東西...我們要指向圖像數據了.要跳過頭信息(lpbi-> biSize).一件事直到寫本文時我才意識到:也要跳過任何的色彩信息.所以要跳過biClrUsed*sizeof(RGBQUAD)(譯者:我想他是說要跳過調色板信息).做完這一切,我們就得到圖像數據的指針了(pdata).

 

也要把動畫的每一幀的大小轉爲紋理能用的大小,還要把數據轉爲RGB數據.這用到DrawDibDraw(...).一個大概的解釋.我們能直接寫設定的DIB圖像.那就是DrawDibDraw(...)所做的.第一參數是DrawDib   DC的句柄.第二參數是DC的句柄.接下來用左上角(0,0)和右下角(256,256)構成目標矩形.lpbi指向剛讀的幀的bitmapinfoheader信息.pdata是剛讀的幀的圖像數據指針.再把源圖象(剛讀的幀)的左上角設爲(0,0),右下角設爲(幀寬,幀高).最後的參數應設爲0.這個方法可把任何大小、色深的圖像轉爲256*256*24bit的圖像.

void  GrabAVIFrame(int   frame)//   從流中抓取一幀

{

LPBITMAPINFOHEADER   lpbi;//  存位圖的頭信息

lpbi  =  (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf,   frame);//  從AVI流中得到數據

pdata=(char   *)lpbi+lpbi-> biSize+lpbi->biClrUsed   *   sizeof(RGBQUAD);//   數據指針,由AVIStreamGetFrame返回(跳過頭  

//信息和色彩信息)

//   把數據轉爲所需格式

DrawDibDraw   (hdd,  hdc,   0,   0,  256,   256,   lpbi,  pdata,   0,   0,  width,   height,   0);

  我們得到動畫的每幀數據(紅藍數據顛倒的).爲解決這個問題,我們的高速代碼flipIt(...).記住,data是指向DIB比特值位置的指針的指針變量.這意味着調用DrawDibDraw後,data指向一個調整過大小(256*256),修改過色深(24bits)的位圖數據.  

flipIt(data); //   交換紅藍數據

//   更新紋理

。。。。。這裏保存或位圖

}

接下來的部分當程序退出時調用,我們關掉DrawDib   DC,釋放佔用的資源.然後釋放AVI   GetFrame資源.最後釋放AVI流和文件.    

void  CloseAVI(void)//   關掉AVI資源

{

DeleteObject(hBitmap);//釋放設備無關位圖信息

DrawDibClose(hdd); //   關掉DrawDib   DC

AVIStreamGetFrameClose(pgf);//   釋放AVI   GetFrame資源

AVIStreamRelease(pavi);//   釋放AVI流

AVIFileExit();//   釋放AVI文件

}

void  flipIt(void*   buffer)//   交換紅藍數據(256x256)

{

void*  b   =   buffer;//  緩衝指針

__asm//  彙編代碼

{

mov  ecx,   256*256 //   設置計數器

mov  ebx,   b//   ebx存數據指針

label: //  循環標記

mov  al,[ebx+0]//   把ebx位置的值賦予al

mov  ah,[ebx+2]//   把ebx+2位置的值賦予ah

mov  [ebx+2],al//   把al的值存到ebx+2的位置

mov  [ebx+0],ah//   把ah的值存到ebx+0的位置

 

add  ebx,3 //   向前走3個字節

dec  ecx //   循環計數器減1

jnz  label //   ecx非0則跳至label

}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章