下面的代碼以只讀方式打開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
}
}