需求:從 AVI 視頻任意幀擷取畫面存成 BMP。DirectShow 雖強大但過重,**AVIFile API (vfw.h)** 輕量、原生、無需額外運行時,是 VC++ 擷取幀的首選。本文封裝 `CAvi` 類,實現開啟、定位、讀取、存檔全流程。
AVIFileInit() 初始化程式庫AVIFileOpen() 取得 `PAVIFILE`AVIFileInfo() 取得寬高、幀數、壓縮格式AVIFileGetStream(..., streamtypeVIDEO, 0) 取得視頻流 `PAVISTREAM`AVIStreamRead() 讀取指定幀 → DIB 緩衝區AVIFileExit() 清理
class CAvi {
public:
int cx, cy; // 影像寬高
BYTE* pData; // 幀像素緩衝區 (RGB24)
BITMAPINFO* m_pBMI; // BMP 資訊頭
PAVISTREAM pavi; // 視頻流介面
PAVIFILE pfile; // 檔案介面
AVIFILEINFO* pfi; // 檔案資訊
CAvi() { AVIFileInit(); cx=cy=0; pData=NULL; m_pBMI=NULL; pavi=NULL; pfile=NULL; pfi=NULL; }
~CAvi() { Cleanup(); AVIFileExit(); }
BOOL Create(LPCTSTR lpszPath); // 開啟檔案、初始化所有成員
BOOL ReadFrame(int nFrame); // 讀取第 n 幀到 pData
BOOL SaveBMP(LPCTSTR lpszBmp); // 將 pData 存為 BMP
private:
void Cleanup();
};
BOOL CAvi::Create(LPCTSTR lpszPath) {
HRESULT hr = AVIFileOpen(&pfile, lpszPath, OF_READ, NULL);
if (FAILED(hr)) return FALSE;
pfi = new AVIFILEINFO;
hr = AVIFileInfo(pfile, pfi, sizeof(AVIFILEINFO));
if (FAILED(hr)) return FALSE;
cx = pfi->dwWidth; cy = pfi->dwHeight;
hr = AVIFileGetStream(pfile, &pavi, streamtypeVIDEO, 0);
if (FAILED(hr)) return FALSE;
// 建立 BITMAPINFO (24-bit RGB, 自下而上)
m_pBMI = (BITMAPINFO*)new BYTE[sizeof(BITMAPINFOHEADER)];
m_pBMI->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
m_pBMI->bmiHeader.biWidth = cx;
m_pBMI->bmiHeader.biHeight = cy; // 正數 = 底部在前 (DIB)
m_pBMI->bmiHeader.biPlanes = 1;
m_pBMI->bmiHeader.biBitCount = 24;
m_pBMI->bmiHeader.biCompression = BI_RGB;
m_pBMI->bmiHeader.biSizeImage = cx * cy * 3;
pData = new BYTE[cx * cy * 3];
return TRUE;
}
BOOL CAvi::ReadFrame(int nFrame) {
// AVIStreamRead: 從 nFrame 開始讀 1 幀
LONG cbBuffer = cx * cy * 3;
HRESULT hr = AVIStreamRead(pavi, nFrame, 1, pData, cbBuffer, NULL, NULL);
return SUCCEEDED(hr);
}
BOOL CAvi::SaveBMP(LPCTSTR lpszBmp) {
if (!pData || !m_pBMI) return FALSE;
BITMAPFILEHEADER bfh = {0};
bfh.bfType = 0x4D42; // 'BM'
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bfh.bfSize = bfh.bfOffBits + m_pBMI->bmiHeader.biSizeImage;
CFile file(lpszBmp, CFile::modeCreate | CFile::modeWrite);
file.Write(&bfh, sizeof(bfh));
file.Write(&m_pBMI->bmiHeader, sizeof(BITMAPINFOHEADER));
file.Write(pData, m_pBMI->bmiHeader.biSizeImage);
return TRUE;
}
CAvi avi;
if (avi.Create(_T("C:\\Video\\demo.avi"))) {
if (avi.ReadFrame(100)) { // 0-based
avi.SaveBMP(_T("C:\\Output\\frame_100.bmp"));
AfxMessageBox(_T("擷取成功!"));
} else {
AfxMessageBox(_T("幀索引超出範圍或讀取失敗"));
}
} else {
AfxMessageBox(_T("無法開啟 AVI 檔案"));
}
| 議題 | 說明 | 對策 |
|---|---|---|
| 壓縮格式不支援 | AVIStreamRead 回傳錯誤 (如 MJPEG、H.264 無解碼器) | 系統需安裝對應 Video for Windows (VFW) 解碼器;或改用 DirectShow/ICM |
| 色序錯誤 (藍紅互換) | AVI 多為 RGB24,BMP 也是 RGB,但部分編碼器輸出 BGR | 讀取後手動交換 R/B 通道,或用 `biCompression = BI_BITFIELDS` 指定遮罩 |
| 大檔案記憶體不足 | 一次性分配全影像緩衝 | 改用 `AVIStreamRead` 分塊讀取、或 `AVIStreamGetFrameOpen` 逐幀解碼 |
| 非標準幀率/可變幀率 | 索引對應時間複雜 | 用 `AVIStreamTimeToSample` / `AVIStreamSampleToTime` 換算 |
| 多音軌/字幕流 | AVIFileGetStream 需指定 streamtypeAUDIO/streamtypeTEXT | 同樣 API,僅 streamtype 不同 |
`AVIFile API` 雖屬上世代技術,但在 **舊專案維護、輕量級工具、無網路環境部署** 仍具不可替代的優勢:零依賴、幾十行代碼搞定開啟→讀幀→存 BMP。封裝好 `CAvi` 類,下次需求來時,`avi.Create → avi.ReadFrame(n) → avi.SaveBMP` 三行解決,優雅高效。