原文地址: http://blog.youkuaiyun.com/ictwangbiao/article/details/8181710
关于怎样枚举设备、建立Filter Graph等问题,我就不多说了,说也说不清楚,因为我也是才接触DirectShow。网上这类资料还是很多,百度知道的比我多多了。这里主要介绍一下自己在学习了StillCap例子之后,实现利用SampleGrabber捕获摄像头每一帧图像的过程。过程中遇到的印象深刻的问题会在下一篇博文中介绍。废话不说了,开门见山吧。
大致思路与代码如下:
1、从ISampleGrabber实例化一个sample grabber,充当一个Transform Filter
- CComPtr<ISampleGrabber> m_pSampleGrabber;
2、设置相关参数,将Grabber注册并且加入到m_pGB这个Filter Graph中
- //Add SampleGrabber
- // create a sample grabber
- //
- hr = m_pSampleGrabber.CoCreateInstance( CLSID_SampleGrabber );
- if( !m_pSampleGrabber )
- {
- MessageBox(NULL, L"Could not create SampleGrabber (is qedit.dll registered?)", L"", MB_OK);
- return hr;
- }
- CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabBase( m_pSampleGrabber );
- // force it to connect to video, 24 bit
- //
- CMediaType VideoType;
- VideoType.SetType( &MEDIATYPE_Video );
- VideoType.SetSubtype( &MEDIASUBTYPE_RGB24 );
- VideoType.SetFormatType(&FORMAT_VideoInfo);
- hr = m_pSampleGrabber->SetMediaType( &VideoType ); // shouldn't fail
- if (FAILED(hr))
- {
- MessageBox(NULL, L"不能初始化SampleGrabber媒体类型。", L"", MB_OK);
- return hr;
- }
- hr = m_pGB->AddFilter( pGrabBase, L"Grabber" );
- if( FAILED( hr ) )
- {
- MessageBox(NULL, L"Could not put sample grabber in graph", L"", MB_OK);
- return hr;
- }
3、连接成功之后,将SampleGrabber作为一个中间filter,利用CSampleGrabber的实例调用回调函数BufferCB,从m_pBF这个Source Filter中得到摄像头设备捕获的每一帧数据,存为Sample,并且将数据原封不动传入m_pVMR中,用于预览
- hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, m_pBF, pGrabBase, m_pVMR);
- if (FAILED(hr))
- {
- MessageBox(NULL, L"Could not put sample into sample grabber", L"", MB_OK);
- return hr;
- }
- //Set mCB
- AM_MEDIA_TYPE mt;
- hr = m_pSampleGrabber->GetConnectedMediaType( &mt );
- if ( FAILED( hr) )
- {
- MessageBox( NULL, TEXT("Could not read the connected media type"),L"", MB_OK);
- return hr;
- }
- VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*) mt.pbFormat;
- mCB.IWidth = vih->bmiHeader.biWidth;
- mCB.IHeight = vih->bmiHeader.biHeight;
- FreeMediaType( mt );
- hr = RenderFileList(m_pGB,m_pVMR );
- // don't buffer the samples as they pass through
- //
- hr = m_pSampleGrabber->SetBufferSamples( TRUE);
- // only grab one at a time, stop stream after
- // grabbing one sample
- //
- hr = m_pSampleGrabber->SetOneShot( FALSE );
- // set the callback, so we can grab the one sample
- //
- hr = m_pSampleGrabber->SetCallback( &mCB, 1 );
- if (FAILED(hr))
- {
- MessageBox(NULL, L"Could not call callback method BufferCB().", L"", MB_OK);
- return hr;
- }
- pGrabBase.Release();
4、从ISampleGrabber继承类CSampleGrabber,用于处理捕获的视频帧sample,重写AddRef, Release, QueryInterface, SampleCB和BufferCB关键函数,加入自己的函数FormatImage(用于转化内存数据为bmp文件数据,并存入内存),GetImgFileData(得到内存中bmp文件数据),SetImgFileData(防止内存泄露)
- //SampleGrabber
- // Global data
- #define WM_CAPTURE_BITMAP WM_APP + 1
- BOOL g_bOneShot=TRUE;
- // Structures
- typedef struct _callbackinfo
- {
- double dblSampleTime;
- long lBufferSize;
- BYTE *pBuffer;
- BITMAPINFOHEADER bih;
- } CALLBACKINFO;
- CALLBACKINFO cb={0};
- class CSampleGrabberCB : public ISampleGrabberCB
- {
- private:
- LPBITMAPFILEHEADER m_pFileHeader ; // Bmp文件头
- LPBITMAPINFOHEADER m_pBmpInfo ; // Bmp信息头指针
- BYTE* m_pImgFileData; //Bmp文件数据
- BOOL m_bViladImage ;
- LPBYTE m_pBitmapHeader ;
- LPVOID m_pvColorTable ; //调色板指针
- public:
- // these will get set by the main thread below. We need to
- // know this in order to write out the bmp
- long IWidth;
- long IHeight;
- CSampleGrabberCB( )
- {
- m_pFileHeader = new BITMAPFILEHEADER ;
- m_pBitmapHeader = new BYTE[sizeof(BITMAPINFOHEADER)] ;
- }
- // fake out any COM ref counting
- //
- STDMETHODIMP_(ULONG) AddRef() { return 2; }
- STDMETHODIMP_(ULONG) Release() { return 1; }
- // fake out any COM QI'ing
- //
- STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
- {
- if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown )
- {
- *ppv = (void *) static_cast<ISampleGrabberCB*> ( this );
- return NOERROR;
- }
- return E_NOINTERFACE;
- }
- // we don't implement this interface for this example
- //
- STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample )
- {
- return 0;
- }
- // As a workaround, copy the bitmap data during the callback,
- // post a message to our app, and write the data later.
- //
- STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize )
- {
- // this flag will get set to true in order to take a picture
- //
- if( !g_bOneShot )
- return 0;
- if (!pBuffer)
- {
- return E_POINTER;
- }
- if( cb.lBufferSize < lBufferSize )
- {
- delete [] cb.pBuffer;
- cb.pBuffer = NULL;
- cb.lBufferSize = 0;
- }
- // Since we can't access Windows API functions in this callback, just
- // copy the bitmap data to a global structure for later reference.
- cb.dblSampleTime = dblSampleTime;
- // If we haven't yet allocated the data buffer, do it now.
- // Just allocate what we need to store the new bitmap.
- if (!cb.pBuffer)
- {
- cb.pBuffer = new BYTE[lBufferSize];
- cb.lBufferSize = lBufferSize;
- }
- if( !cb.pBuffer )
- {
- cb.lBufferSize = 0;
- return E_OUTOFMEMORY;
- }
- //Get bmp information
- BITMAPINFOHEADER bih;
- memset( &bih, 0, sizeof( bih ) );
- bih.biSize = sizeof( bih );
- bih.biWidth = IWidth;
- bih.biHeight = IHeight;
- bih.biPlanes = 1;
- bih.biBitCount = 24;
- memcpy(&(cb.bih), &bih, sizeof(bih));
- // Copy the bitmap data into our global buffer
- memcpy(cb.pBuffer, pBuffer, lBufferSize);
- // Post a message to our application, telling it to come back
- // and write the saved data to a bitmap file on the user's disk.
- SendMessage(m_hWnd, WM_CAPTURE_BITMAP, 0, 0L);
- return 0;
- }
- BOOL FormatImage( BYTE *lpImageData, int nBitCount, int nWidth, int nHeight )
- {
- m_bViladImage = FALSE ;
- int nKlsBmpBitCount ;
- int nImgWidth = nWidth ;
- int nImgHeight = nHeight ;
- if (nBitCount == 8 || nBitCount == 24 || nBitCount == 32)
- {
- nKlsBmpBitCount = nBitCount;
- }
- else
- {
- return m_bViladImage ;
- }
- int nDataWidth = nKlsBmpBitCount / 8 * nWidth ;
- nDataWidth = ( nDataWidth % 4 == 0 ) ? nDataWidth : ( ( nDataWidth / 4 + 1 ) * 4 ) ;
- //m_pFileHeader = new BITMAPFILEHEADER ;
- m_pFileHeader->bfType = 0x4d42 ;
- m_pFileHeader->bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + nDataWidth*nImgHeight ;
- m_pFileHeader->bfReserved1 = 0 ;
- m_pFileHeader->bfReserved2 = 0 ;
- if ( nBitCount == 8 )
- {
- int nBmpInfoSize = sizeof(BITMAPFILEHEADER)
- + sizeof(BITMAPINFOHEADER)
- + 256 * 4 ;
- m_pFileHeader->bfOffBits = nBmpInfoSize ;
- m_pBitmapHeader = new BYTE[nBmpInfoSize] ;
- m_pBmpInfo = (LPBITMAPINFOHEADER)m_pBitmapHeader ;
- m_pvColorTable = m_pBitmapHeader + sizeof(BITMAPINFOHEADER) ;
- LPRGBQUAD pDibQuad = (LPRGBQUAD)(m_pvColorTable) ;
- for ( int c=0; c<256; ++c )
- {
- pDibQuad[c].rgbRed = c ;
- pDibQuad[c].rgbGreen = c ;
- pDibQuad[c].rgbBlue = c ;
- pDibQuad[c].rgbReserved = 0 ;
- }
- }
- else
- {
- m_pFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) ;
- //m_pBitmapHeader = new BYTE[sizeof(BITMAPINFOHEADER)] ;
- m_pBmpInfo = (LPBITMAPINFOHEADER)m_pBitmapHeader ;
- m_pvColorTable = NULL ;
- }
- m_pBmpInfo->biBitCount = nKlsBmpBitCount ;
- m_pBmpInfo->biWidth = nImgWidth ;
- m_pBmpInfo->biHeight = nImgHeight ;
- m_pBmpInfo->biPlanes = 1 ;
- m_pBmpInfo->biSize = sizeof(BITMAPINFOHEADER) ;
- m_pBmpInfo->biSizeImage = nImgWidth * nImgHeight * nKlsBmpBitCount / 8 ;
- m_pBmpInfo->biClrImportant = 0 ;
- m_pBmpInfo->biClrUsed = 0 ;
- m_pBmpInfo->biCompression = 0 ;
- m_pBmpInfo->biXPelsPerMeter = 0 ;
- m_pBmpInfo->biYPelsPerMeter = 0 ;
- //m_pImgFileData = new BYTE[nDataWidth*nImgHeight] ;
- SetImgFileData(nDataWidth, nImgHeight);
- if ( nBitCount == 8 )
- {
- if ( nImgWidth % 4 == 0 )
- {
- memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;
- memcpy( m_pImgFileData, lpImageData, cb.lBufferSize) ;
- }
- else
- {
- memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;
- for ( int i=0; i<nImgHeight; i++ )
- {
- memcpy( m_pImgFileData + i*nDataWidth, lpImageData + i*nImgWidth, nImgWidth ) ;
- }
- }
- }
- else if ( nBitCount == 24 )
- {
- if ( nImgWidth % 4 == 0 )
- {
- memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;
- memcpy( m_pImgFileData, lpImageData, cb.lBufferSize ) ;
- }
- else
- {
- memset( m_pImgFileData, 0, nDataWidth*nImgHeight ) ;
- for ( int i=0; i<nImgHeight; i++ )
- {
- memcpy( m_pImgFileData + i*nDataWidth, lpImageData + i*3*nImgWidth, 3*nImgWidth ) ;
- }
- }
- }
- else if ( nBitCount == 32 )
- {
- memcpy( m_pImgFileData, lpImageData,cb.lBufferSize ) ;
- }
- m_bViladImage = TRUE ;
- return m_bViladImage ;
- }
- BYTE* GetImgFileData()
- {
- return m_pImgFileData;
- }
- void SetImgFileData(int dataWidth, int dataHeight)
- {
- if (!m_pImgFileData)
- {
- m_pImgFileData = new BYTE[dataWidth*dataHeight];
- }
- else
- {
- delete [] m_pImgFileData;
- m_pImgFileData = NULL;
- m_pImgFileData = new BYTE[dataWidth*dataHeight];
- }
- }
- };
- //
- //
- // This semi-COM object will receive sample callbacks for us
- //
- //
- CSampleGrabberCB mCB;
- //SampleGrabber
5、得到Sample之后,放入到回调函数BufferCB中进行处理,从而得到必要的cb.pBuffer数据块,并将大小,图像的头信息bih存入结构体cb中
- //From Step 3
- // set the callback, so we can grab the one sample
- //
- hr = m_pSampleGrabber->SetCallback( &mCB, 1 );
- //From BufferCB
- //Get bmp information
- BITMAPINFOHEADER bih;
- memset( &bih, 0, sizeof( bih ) );
- bih.biSize = sizeof( bih );
- bih.biWidth = IWidth;
- bih.biHeight = IHeight;
- bih.biPlanes = 1;
- bih.biBitCount = 24;
- memcpy(&(cb.bih), &bih, sizeof(bih));
- // Copy the bitmap data into our global buffer
- memcpy(cb.pBuffer, pBuffer, lBufferSize);
以下两步均在使用数据的函数中实现,一般是在同一函数中使用
6、得到cb中的pBuffer数据和相关信息,存入指定内存
- BYTE* pImageData = new BYTE[cb.lBufferSize];
- CopyMemory(pImageData, cb.pBuffer, cb.lBufferSize);
7、,利用FormatImage对内存数据进行转化,得到bmp数据,并存入指定内存,通过GetImgFileData得到bmp数据,放入到压缩函数中进行压缩,放入公共内存中,传入到虚拟摄像头中。
- if (mCB.FormatImage(pImageData, 24, cb.bih.biWidth, cb.bih.biHeight))
- {
- //此函数用于转化m_pImgFileData为指定长宽的bmp图片信息,没有需求就不用了
- ScaleRgb24BmpData(TransformBuffer, 320, 240,( mCB.GetImgFileData()),cb.bih.biWidth, cb.bih.biHeight);
- ZeroMemory(m_pData, BUFFER_SIZE);
- //写入共享内存
- CopyMemory(m_pData, TransformBuffer, BUFFER_SIZE);
- }
- //to free memory
- delete pImageData;
- //OVER ICTwangbiao