025 dshow中使用Sample Grabber filter抓取图像

Post date: 2015/4/17 上午 01:17:44

sample Grabber使用两种模式抓取图像:缓冲模式和回调模式, 缓冲模式向下传递采样时拷贝每个采样, 而回调模式对于每个采样调用程序定义的回调函数。 回调模式是动态加载filter, 影响程序性能, 甚至引起死锁。 其中的原因是如果采样是microsoft directdraw surface, 在回调期间surface被锁定。 win16 lock可以被好的锁定, 但两个会引起潜在的死锁。 具体在dshow文件中有详细描述。 缓冲区激活与否取决于ISampleGrabber::SetBufferSample的函数取值 sample grabber可以从文件源中或实时源时抓取, 本文在下面详细描述。 首先, 回调函数是从ISampleGrabberCB中派生出自己的类, 然后实现其虚函数, 这个代码在dshow的sdk的示例程序(DXSDKROOT /Samples/C++/DirectShow/Editing/GrabBitmaps)中完整的代码实现。 这里不再给出。 主要内容: 1. 缓冲区模式从文件源中抓取 2. 缓冲区模式从实时源中抓取 3. 回调模式从文件源中抓取 4. 回调模式从实时源中抓取 5. 通过GetCurrentImage 6. IMediaDet 1. 缓冲区模式从文件源中抓取 效果图: 1) 初始化GraphBuilder HRESULT hr; CComPtr<IGraphBuilder>pGraph; pGraph.CoCreateInstance(CLSID_FilterGraph); 2) 初始化sample grabber IBaseFilter *pGrabberF = NULL; hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,IID_IBaseFilter, (void**)&pGrabberF); if (FAILED(hr)){ return; } hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber"); if (FAILED(hr)) { return; } 3) 查询并设置媒体类型 ISampleGrabber *pGrabber; pGrabberF->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber); AM_MEDIA_TYPE mt; ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE)); mt.majortype = MEDIATYPE_Video; mt.subtype = MEDIASUBTYPE_RGB24; hr = pGrabber->SetMediaType(&mt); 4) 建立文件源并连接graph, source, grabber TCHAR wszFileName[MAX_PATH]; wcscpy(wszFileName, L"I://Documents//example.avi"); IBaseFilter *pSrc; hr = pGraph->AddSourceFilter(wszFileName, L"Source", &pSrc); if (FAILED(hr)) { return; } hr = ConnectFilters(pGraph, pSrc, pGrabberF); 5) 把NULL Renderer增加进去 IBaseFilter *pNull = NULL; hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>(&pNull)); hr = pGraph->AddFilter(pNull, L"NullRenderer"); hr = ConnectFilters(pGraph, pGrabberF, pNull); 6) 检查连接的媒体类型 hr = pGrabber->GetConnectedMediaType(&mt); if (FAILED(hr)) { return; } // Examine the format block. VIDEOINFOHEADER *pVih; if ((mt.formattype == FORMAT_VideoInfo) && (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) && (mt.pbFormat != NULL) ) { pVih = (VIDEOINFOHEADER*)mt.pbFormat; } else { // Wrong format. Free the format block and return an error. FreeMediaType(mt); return ;//VFW_E_INVALIDMEDIATYPE; } // Set one-shot mode and buffering. hr = pGrabber->SetOneShot(TRUE); hr = pGrabber->SetBufferSamples(TRUE); 7) 搜索到指定的时间 CComQIPtr<IMediaControl, &IID_IMediaControl>pControl(pGraph); CComQIPtr<IMediaEventEx,&IID_IMediaEvent>pEvent(pGraph); IMediaSeeking *pSeeking = NULL; pGraph->QueryInterface(IID_IMediaSeeking, (void**)&pSeeking); LONGLONG pCurrentPos = ONE_SECOND * 20;//20秒 hr = pSeeking->SetPositions(&pCurrentPos, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning ); pControl->Run(); // Run the graph. long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); // Wait till it's done. 8) 取得当前的buffer数据 //find the required buffer size long cbBuffer = 0; hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL); LONGLONG currentPos; pSeeking->GetCurrentPosition(&currentPos); //Allocate the array and call the method a second time to copy the buffer: char *pBuffer = new char[cbBuffer]; if (!pBuffer) { return; } hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)(pBuffer)); 9) 写到BMP文件中 HANDLE hf = CreateFile(L"C://Example.bmp", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); if (hf == INVALID_HANDLE_VALUE) { return; } // Write the file header. BITMAPFILEHEADER bfh; ZeroMemory(&bfh, sizeof(bfh)); bfh.bfType = 'MB'; // Little-endian for "MB". bfh.bfSize = sizeof( bfh ) + cbBuffer + sizeof(BITMAPINFOHEADER); bfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + sizeof(BITMAPINFOHEADER); DWORD dwWritten = 0; WriteFile( hf, &bfh, sizeof( bfh ), &dwWritten, NULL ); // Write the bitmap format BITMAPINFOHEADER bih; ZeroMemory(&bih, sizeof(bih)); bih.biSize = sizeof( bih ); bih.biWidth = pVih->bmiHeader.biWidth; bih.biHeight = pVih->bmiHeader.biHeight; bih.biPlanes = pVih->bmiHeader.biPlanes; bih.biBitCount = pVih->bmiHeader.biBitCount; dwWritten = 0; WriteFile(hf, &bih, sizeof(bih), &dwWritten, NULL); //write the bitmap bits dwWritten = 0; WriteFile( hf, pBuffer, cbBuffer, &dwWritten, NULL ); CloseHandle( hf ); 10) 释放相关资源 pControl->Stop(); SAFE_RELEASE(pSrc); SAFE_RELEASE(pNull); SAFE_RELEASE(pGrabberF); 11) 其它 NullRenderer可以用IVideoWindow代替 CComQIPtr< IVideoWindow, &IID_IVideoWindow > pWindow = pGraph; if (pWindow) { hr = pWindow->put_AutoShow(OAFALSE); } 2. 缓冲区模式从实时源中抓取 此方法可以参照1、4的实现 3. 回调模式从文件源中抓取 此代码在DXSDKROOT /Samples/C++/DirectShow/Editing/GrabBitmaps中有完整实现 效果图 4. 回调模式从实时源中抓取 效果图 相关初始化代码略 1) 创建并增加sample grabber filter到graph中 hr = m_pSampleGrabber.CoCreateInstance(CLSID_SampleGrabber); if(FAILED(hr)) { return hr; } CComQIPtr<IBaseFilter, &IID_IBaseFilter>pGrabBase(m_pSampleGrabber); hr = m_pGraphBuilder->AddFilter(pGrabBase, L"Grabber"); hr = m_pCaptureGB2->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, m_pVCap, pGrabBase, NULL); if(hr == VFW_S_NOPREVIEWPIN) { } else if(hr != S_OK) { hr = m_pCaptureGB2->RenderStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video, m_pVCap, pGrabBase, NULL); if(hr == VFW_S_NOPREVIEWPIN) { } else if(hr != S_OK) { m_bPreviewGraphBuilt = FALSE; return FALSE; } } 2) 设置媒体类型 // 根据显示器的设备来设置RGB CDC *pDC = GetDC(); int iBitDepth = GetDeviceCaps(pDC->GetSafeHdc(), BITSPIXEL); ZeroMemory(&g_MediaType, sizeof(AM_MEDIA_TYPE)); g_MediaType.majortype = MEDIATYPE_Video; switch(iBitDepth) { case 8: g_MediaType.subtype = MEDIASUBTYPE_RGB8; break; case 16: g_MediaType.subtype = MEDIASUBTYPE_RGB555; break; case 24: g_MediaType.subtype = MEDIASUBTYPE_RGB24; break; case 32: g_MediaType.subtype = MEDIASUBTYPE_RGB32; break; default: return E_FAIL; } hr = m_pSampleGrabber->SetMediaType(&g_MediaType); if ( FAILED( hr) ) { return hr; 3) 检查连接的媒体类型并设置回调函数 hr = m_pSampleGrabber->GetConnectedMediaType(&g_MediaType ); hr = m_pSampleGrabber->SetBufferSamples(FALSE); hr = m_pSampleGrabber->SetOneShot(FALSE); hr = m_pSampleGrabber->SetCallback( &g_SGCallback, 1 ); 4) 连接IVideoWindow hr = m_pGraphBuilder->QueryInterface(IID_IVideoWindow, (void **)&m_ pWindow); if (pWindow) { hr = pWindow->put_AutoShow(OAFALSE); } 5. 通过GetCurrentImage 1) IBasicVideo::GetCurrentImage 在使用DirectDraw加速时renderer会失败, 需要用户硬件的支持, 所以这个方法不可靠。 在调用这个接口要先暂停video renderer。 可以通过IMediaControl::GetState确定状态。 2) IVMRWindowlessControl::GetCurrentImage VMR没有上述的问题, 可以在graph运行、停止或暂停时抓取 在我的“使用VMR9采集n个视频的一帧到一张位图”中有实现 6. IMediaDet 没实现