DirectXShow开发-实现视频预览和捕获静态图片功能出现的问题

mf1983 2014-03-21 04:35:33
最近在做基于DirectXShow的视频预览及捕获静态图片的工作,使用VS2005,CLR C++开发
要完成的功能:
1 一个pictureBox中实现视频预览
2 通过button按钮事件从视频预览的video stream中捕获一张图片数据,并在另一个pictureBox中显示出来
3 捕获的同时视频预览窗口可以有暂时的停顿,捕获完毕要继续实现视频预览的功能

查阅了MSDN,也参考了DirectXShow的demo(Microsoft DirectX 9.0 SDK (Summer 2004)\Samples\C++\DirectShow目录下的Capture和Editing下的demo),采用了Sample Grabber Filter,使用SetBufferSamples( TRUE ) ;SetOneShot( FALSE )的模式.

问题是:
1 单独实现视频预览或单独实现从capture video stream中捕获一张图片都没问题
2 在预览视频的同时捕获图片出现问题,GetCurrentBuffer时出错

IMediaControl::Run之后使用GetCurrentBuffer获取图片大小出错,通过调试发现是因为Filter Graph的状态不对,MSDN中说调用GetCurrentBuffer方法的时候“Do not call this method while the filter graph is running.”。那我应该何时调用呢?在Run后调用Stop和Pause方法都不行,调用IMediaEvent::GetEvent方法测试,使用以下代码:

while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
//add the evCode to a int array[]
}

发现一共返回了5个evCode,从array[0] --array[4]分别是:EC_PALETTE_CHANGED,EC_VIDEO_SIZE_CHANGED,EC_CLOCK_CHANGED,EC_PAUSED,EC_PAUSED
然后调用GetCurrentBuffer依然出错

难道除了Sleep就没有别的方法了么?
SetBufferSamples( TRUE )之后DirectShow到底做了什么?对Filter Graph有什么影响?如何知道何时调用GetCurrentBuffer?有没有详细的资料?

附代码:

HRESULT DSComment::CreatFilterGraph(){

HRESULT hr;
cli::pin_ptr<IGraphBuilder *> ppGraphManager = &pGraphManager;
cli::pin_ptr<ICaptureGraphBuilder2 *> ppCapture = &pCapture;

if(!ppGraphManager || !ppCapture){
return E_POINTER;
}

hr = CoCreateInstance (
CLSID_FilterGraph,
NULL,
CLSCTX_INPROC_SERVER, //for add grephedit
IID_IGraphBuilder,
(void **) ppGraphManager
);
if (FAILED(hr)){
MessageBox::Show("Initial IFilterGraph2 failed!");
return E_NOINTERFACE;
}



// Create the capture graph builder
hr = CoCreateInstance (
CLSID_CaptureGraphBuilder2,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICaptureGraphBuilder2,
(void **) ppCapture
);
if (FAILED(hr)){
MessageBox::Show("Initial ICaptureGraphBuilder2 failed!");
return E_NOINTERFACE;
}
return S_OK;
}


HRESULT DSComment::CreatGrabberFilter(){

HRESULT hr;
cli::pin_ptr<IBaseFilter *> ppGrabberBaseFilter = &pGrabberBaseFilter;

hr = CoCreateInstance(
CLSID_SampleGrabber,
NULL,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
(void**)ppGrabberBaseFilter
);


if (FAILED(hr))
{
MessageBox::Show("Couldn't create sample grabber filter base!");
return E_NOINTERFACE;
}


cli::pin_ptr<ISampleGrabber *> ppGrabber = &pGrabber;

pGrabberBaseFilter->QueryInterface(IID_ISampleGrabber, (void**)ppGrabber);

AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
hr = pGrabber->SetMediaType(&mt);
FreeMediaType(mt);

return hr;
}


HRESULT DSComment::CreatCaptureFilter(){

HRESULT hr;
ULONG cFetched;
const char* CAPTUREDEVNAME;

CAPTUREDEVNAME = "USB 视频设备";


cli::pin_ptr<IBaseFilter *> ppCaptureBaseFilter = &pCaptureBaseFilter;

// Create the system device enumerator
ICreateDevEnum *pDevEnum =NULL;

hr = CoCreateInstance (
CLSID_SystemDeviceEnum,
NULL,
CLSCTX_INPROC,
IID_ICreateDevEnum,
(void **) &pDevEnum
);
if (FAILED(hr)){
MessageBox::Show("Couldn't create system enumerator!");
return E_NOINTERFACE;
}

// Create an enumerator for the video capture devices
IEnumMoniker *pClassEnum = NULL;

hr = pDevEnum->CreateClassEnumerator(
CLSID_VideoInputDeviceCategory,
&pClassEnum,
0
);

if (FAILED(hr)){
MessageBox::Show("Couldn't create class enumerator!");
return E_NOINTERFACE;
}

// If there are no enumerators for the requested type, then
// CreateClassEnumerator will succeed, but pClassEnum will be NULL.
if (pClassEnum == NULL){
MessageBox::Show("No video capture device was detected.\r\n\r\n \
This sample requires a video capture device, such as a USB WebCam,\r\n \
to be installed and working properly. The sample will now close. \
No Video Capture Hardware");
return E_FAIL;
}

IMoniker *pMoniker = NULL;

// Use the first video capture device on the device list.
// Note that if the Next() call succeeds but there are no monikers,
// it will return S_FALSE (which is not a failure). Therefore, we
// check that the return code is S_OK instead of using SUCCEEDED() macro.
if (S_OK == (pClassEnum->Next (1, &pMoniker, &cFetched))){

// Bind Moniker to a filter object
hr = pMoniker->BindToObject(0,0,IID_IBaseFilter, (void**)ppCaptureBaseFilter);

IPropertyBag *pPropBag = NULL;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
if (FAILED(hr)){
pMoniker->Release();
return E_NOINTERFACE;
}

VARIANT var;
VariantInit(&var);

// Get description or friendly name.
hr = pPropBag->Read(L"Description", &var, 0);
if (FAILED(hr)){
hr = pPropBag->Read(L"FriendlyName", &var, 0);
}

if (SUCCEEDED(hr)){
String^ strCaptureDevName = String(var.bstrVal).ToString();
if(!strCaptureDevName->CompareTo(String(CAPTUREDEVNAME).ToString())){
hr = S_OK;
}else{
hr = E_FAIL;
return hr;
}
}
pPropBag->Release();
}else{
MessageBox::Show("Unable to access video capture device!");
return E_FAIL;
}


pMoniker->Release();
pClassEnum->Release();
pDevEnum->Release();
return hr;

}



HRESULT DSComment::BuiltFilterGraph(){

HRESULT hr;
pGraphManager->AddFilter(pCaptureBaseFilter, L"Capture Device");
pGraphManager->AddFilter(pGrabberBaseFilter, L"Sample Grabber Filter");

pCapture->SetFiltergraph(pGraphManager);

pCapture->RenderStream(
&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video,
pCaptureBaseFilter,
pGrabberBaseFilter,
NULL

);


cli::pin_ptr<IVideoWindow *> ppVW = &pVW;
cli::pin_ptr<IMediaControl *> ppMC = &pMC;
cli::pin_ptr<IMediaEventEx *> ppME = &pME;

hr = pGraphManager->QueryInterface(IID_IMediaControl,(LPVOID *) ppMC);
if (FAILED(hr)){
MessageBox::Show("Initial IMediaControl failed!");
return E_NOINTERFACE;
}

hr = pGraphManager->QueryInterface(IID_IMediaEvent, (LPVOID *) ppME);
if (FAILED(hr)){
MessageBox::Show("Initial IMediaEventEx failed!");
return E_NOINTERFACE;
}

hr = pGraphManager->QueryInterface(IID_IVideoWindow, (LPVOID *) ppVW);
if (FAILED(hr)){
MessageBox::Show("Initial IVideoWindow failed!");
return E_NOINTERFACE;
}

}

BYTE* DSComment::SnapStillImage(long* size){

HRESULT hr;

pMC->Stop();

hr = pGrabber->SetBufferSamples( TRUE );

// Only grab one at a time, stop stream after
// grabbing one sample
//
hr = pGrabber->SetOneShot( FALSE );

pMC->Run();


//××××××××××××××××××××××××××××××××××××××××××××××××××××××××
//就是在这里除了Sleep难道没有别的方法可以保证GetCurrentBuffer成功??
//×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

long cbBuffer;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
if (FAILED(hr))
{
MessageBox::Show("GetCurrentBuffer Fail!");
}

BYTE *pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
if (!pBuffer)
{
MessageBox::Show("CoTaskMemAlloc Fail!");
}

hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
if (FAILED(hr))
{
MessageBox::Show("GetCurrentBuffer Fail!");

}

hr = pGrabber->SetOneShot( FALSE );

AM_MEDIA_TYPE mt;

hr = pGrabber->GetConnectedMediaType(&mt);
if (FAILED(hr))
{
MessageBox::Show("GetConnectedMediaType Fail!");

}

VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;

long cbBMI = mt.cbFormat - SIZE_PREHEADER;

long captureSize = cbBMI + cbBuffer + sizeof(BITMAPFILEHEADER);
*size = captureSize;
BYTE *pCaptrue = (BYTE*)CoTaskMemAlloc(captureSize);

BITMAPFILEHEADER bmf = { };


bmf.bfType = 'MB';
bmf.bfSize = captureSize;
bmf.bfOffBits = sizeof(bmf) + cbBMI;

memcpy(pCaptrue, &bmf, sizeof(bmf));

memcpy((pCaptrue + sizeof(bmf)), &pVih->bmiHeader, cbBMI);
memcpy((pCaptrue + sizeof(bmf) + cbBMI), pBuffer, cbBuffer);

return pCaptrue;

}
...全文
201 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
mf1983 2014-03-26
  • 打赏
  • 举报
回复
好的,多谢指教,我再研究下
曹大夯 2014-03-25
  • 打赏
  • 举报
回复
用回调函数,在回调函数里面读取Image Buffer是不会影响视频Preview的,除非你在回调函数里面做太多事,有可能影响视频的流畅性。
曹大夯 2014-03-24
  • 打赏
  • 举报
回复
一般情况下,Sample Grabber是用ISampleGrabber::SetCallback method 来获取Snap Image Buffer的。 例子提供的Graph,其Render是Null的,不需要实际显示视频;而你的应用程序,Render不是Null Render,所以应该用Call Back,在CallBack里面获取图像。
mf1983 2014-03-24
  • 打赏
  • 举报
回复
多谢Huntercao的指教 其实我刚开始的时候也加过NullRenderer,但是我觉得后面这个Render类型不是最重要的。 在上面的代码中我用系统自动添加的video render,设置

SetBufferSamples( TRUE ) ;
SetOneShot( TRUE )
也能抓图,只是预览视频的功能停止了。看MSDN上说setoneshot后会给后面的render发一个IPin::endstream(可能不准确,大意是告诉后面的render没有vidoe stream了),这个能否通过操作IPin继续恢复视频预览的功能? 对于GetCurrentBuffer,是不是除了sleep,只能配合setoneshot(TRUE),然后设置IMediaControl::waitforcomple(INFINITE, &EvCode)来完成sample的buffer?不能通过IMediaEvent::getevente或IMediaControl::getstate的方法获得么? 我现在最想知道的是GetCurrentBuffer这条路是否能走的通,实在不行就转callback了,之前也尝试过callback模式,有点问题没有继续深入~~~ 再次感此Huntercao的指教

21,597

社区成员

发帖
与我相关
我的任务
社区描述
硬件/嵌入开发 驱动开发/核心开发
社区管理员
  • 驱动开发/核心开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧