許多影片播放或編輯軟體都提供擷取畫面功能,讓影片工作者能輕鬆將電影特定時刻的畫面擷取並儲存為圖片檔案。那麼,我們該如何自行編程實現這項功能?若您熟悉 MPEG 或 AVI 等常見影片格式,可直接操作影片檔案;若不熟悉這些格式,想用更簡單方法擷取畫面,Microsoft DirectShow 將帶給您極大驚喜。
DirectShow 屬於 DirectX 家族(還包含 Direct3D、DirectInput、DirectDraw、DirectSound 等元件)。使用 DirectShow 開發擷取畫面程式前,必須安裝 DirectX SDK(可從 Microsoft 網站下載,最新版本為 9.0b)。此外,DirectX SDK 以 COM 方式發布,開發人員還需了解 COM 基本原理。若未使用過 COM,建議先閱讀相關入門教學。
安裝完 DirectX 9.0b SDK 後,通常會自動設定好 VC 6.0。若編譯出錯,請檢查 VC 是否包含 DirectX SDK 的標頭檔與程式庫檔:選擇「Tools > Options...」,在彈出的 Options 對話方塊中選擇 Directories 索引標籤,查看 Include files 和 Library files 中是否包含 DirectX SDK 的 Include 路徑與 Lib 路徑,若無則自行加入。
在實現擷取影片畫面過程中,DirectShow 的 IMediaDet 介面將是主角,此介面包含方法可從媒體來源檔案提取重要資訊,如媒體類型、畫面速率甚至視訊串流的單一畫面。
dshow.h、qedit.hstrmiids.libCComPtr 模板宣告介面實例,還需包含 atlbase.h 標頭檔HRESULT 型別成員函式 GrabFrameFromMovie,實現擷取畫面功能。函式內建立 IMediaDet 介面實例,呼叫 CoCreateInstance 並傳入 CLSID_MediaDet 類別識別碼IMediaDet::put_Filename 指定媒體檔案,參數類型為 BSTRIMediaDet::get_OutputStreams 取得影片輸出串流數目,僅關心視訊串流與音訊串流,自動忽略其他串流IMediaDet::put_CurrentStream 指定操作串流,因目的是將畫面儲存為圖片,需定位視訊串流IMediaDet::get_StreamMediaType 取得 VIDEOINFOHEADER 結構,包含描述視訊影像對應點陣圖尺寸、顏色等資訊的 BITMAPINFOHEADERIMediaDet::WriteBitmapBits 將畫面儲存為點陣圖,第一個參數傳遞時間即可指定擷取哪一畫面(傳遞 0 表示擷取第一畫面)HRESULT CFrameGrabberDlg::GrabFrameFromMovie()
{
HRESULT hr;
// 定義 IMediaDet 介面實例
CComPtr<IMediaDet> pDet;
hr = CoCreateInstance(CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
IID_IMediaDet, (void**)&pDet);
if (FAILED(hr)) return hr;
// 將影片檔名轉換為 BSTR 型別
CComBSTR openBSTR(m_editOpenDir);
// 設定 IMediaDet 介面的檔案關聯
hr = pDet->put_Filename(openBSTR);
if (FAILED(hr)) return hr;
// 從影片檢索視訊串流與音訊串流
long lStreams;
hr = pDet->get_OutputStreams(&lStreams);
if (FAILED(hr)) return hr;
// 取出影片的視訊串流
bool bFound = false;
for (int i = 0; i < lStreams; i++)
{
GUID major_type;
hr = pDet->put_CurrentStream(i);
if (SUCCEEDED(hr))
hr = pDet->get_StreamType(&major_type);
if (FAILED(hr)) break;
if (major_type == MEDIATYPE_Video)
{
bFound = true;
break;
}
}
if (!bFound) return VFW_E_INVALIDMEDIATYPE;
long width = 0, height = 0; // 儲存點陣圖寬高(像素)
AM_MEDIA_TYPE mt;
hr = pDet->get_StreamMediaType(&mt);
if (SUCCEEDED(hr))
{
if ((mt.formattype == FORMAT_VideoInfo) &&
(mt.cbFormat >= sizeof(VIDEOINFOHEADER)))
{
// 取得 VIDEOINFOHEADER 結構指標
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)(mt.pbFormat);
width = pVih->bmiHeader.biWidth;
height = pVih->bmiHeader.biHeight;
}
else
hr = VFW_E_INVALIDMEDIATYPE;
MyFreeMediaType(mt); // 釋放 AM_MEDIA_TYPE 結構
}
if (FAILED(hr)) return hr;
CComBSTR saveBSTR(m_editSaveDir);
// 將第一畫面儲存為指定路徑的點陣圖檔案
hr = pDet->WriteBitmapBits(0, width, height, saveBSTR);
if (FAILED(hr)) return hr;
return S_OK;
}
程式執行後,選擇一個影片,指定儲存路徑,點擊「擷取」按鈕,即可將影片第一畫面儲存至指定路徑。也可修改 WriteBitmapBits 方法第一個參數來擷取指定畫面。原始碼 DEBUG 資料夾包含測試影片。該程式在 Windows XP、Visual C++ 6 和 DirectX 9.0b 環境下編譯執行通過。