现在手头上有一个项目就是需要优化采集方案。
我们这边之前使用的是作者Shiqi Yu写的开源代码,底层是基于DShow做的封装。但使用后发现采集到的视频流在进行回显时有点模糊,特别是文字部分。
现在通过万能的网络找到了三种替换方案:WebRTC 接、MediaFoundation、VideoInput。
1、WebRTC:现被Google整编,但由于需要才能下载和更新,所以没有使用。
2、MediaFoundation:微软基于DShow上做的优化框架,回显也明显清晰流畅了许多,但是继承了微软的封装特性,所以想嵌入自己的逻辑,相对于比较麻烦,所以放弃。
获取视频采集设备。
HRESULT hr = S_OK;
IMFAttributes *pAttributes;
if (SUCCEEDED(hr))
{
hr = MFStartup(MF_VERSION); //这是必须的,不然无法绑定回调资源
}
if (SUCCEEDED(hr))
{
hr = MFCreateAttributes(&pAttributes, 1);
}
if (SUCCEEDED(hr))
{
hr = m_pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
);
}
if (SUCCEEDED(hr))
{
hr = MFEnumDeviceSources(m_pAttributes, &m_param.ppDevices, &m_param.count);
}
SafeRelease(&pAttributes);
打开一个采集设备
HRESULT hr = S_OK;
IMFMediaSource *pSource = NULL;
EnterCriticalSection(&m_critsec);
IMFActivate* pActivate = m_param.ppDevices[nCamID];
pActivate->AddRef();
// Create the media source for the device.
if (SUCCEEDED(hr))
{
hr = pActivate->ActivateObject(__uuidof(IMFMediaSource),
(void**)&pSource
);
}
// Get the symbolic link. This is needed to handle device-
// loss notifications. (See CheckDeviceLost.)
if (SUCCEEDED(hr))
{
hr = pActivate->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&m_pwszSymbolicLink,
&m_cchSymbolicLink
);
}
WCHAR *szFriendlyName = NULL;
if (SUCCEEDED(hr))
{
hr = pActivate->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
&szFriendlyName,
NULL
);
}
UINT32 unMaxSize = 0;
if (SUCCEEDED(hr))
{
hr = pActivate->GetUINT32(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_MAX_BUFFERS,
&unMaxSize
);
}
// When the method completes, MFPlay will call OnMediaPlayerEvent
// with the MFP_EVENT_TYPE_MEDIAITEM_CREATED event.
if (SUCCEEDED(hr))
{
hr = CreateSourceReaderAsync(pSource);
}
if (SUCCEEDED(hr))
{
m_pSource = pSource;
m_pSource->AddRef();
}
if (FAILED(hr))
{
CloseCamera();
}
else
{
m_ob = ob;
Start();
}
SafeRelease(&pSource);
SafeRelease(&pActivate);
LeaveCriticalSection(&m_critsec);
HRESULT hr = S_OK;
IMFAttributes *pAttributes = NULL;
IMFMediaType * nativeType = NULL;
hr = MFCreateAttributes(&pAttributes, 2);
if (SUCCEEDED(hr))
{
hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, this);
}
if (SUCCEEDED(hr))
{
hr = MFCreateSourceReaderFromMediaSource(pSource,pAttributes,&m_pSourceReader);
}
if (SUCCEEDED(hr))
{
hr = m_pSourceReader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,NULL,nativeType);
}
SafeRelease(&nativeType);
SafeRelease(&pAttributes);
视频流读取
if (FAILED(hrStatus))
{
hr = hrStatus;
LeaveCriticalSection(&m_critsec);
return hr;
}
if(pSample )
{
DWORD maxLength,currentLength;
BYTE* pBuffer = NULL;
BYTE * pBitmapData = NULL;
IMFMediaBuffer* pMBuffer = NULL;
hr = pSample->GetBufferByIndex(0, &pMBuffer);
//hr = pSample->ConvertToContiguousBuffer(&pMBuffer);
if(SUCCEEDED(hr))
{
hr = pMBuffer->Lock(&pBuffer,&maxLength,¤tLength);
}
if(SUCCEEDED(hr))
{
hr = pMBuffer->Unlock();
if(currentLength > 0)
m_ob->onRGB((const char*)pBuffer,currentLength);
SafeRelease(&pMBuffer);
}
}
3、VideoInput 被OpenCV收入的一个非常实用的开源视频采集库,现在我就使用的这个库。
打开一个采集设备
if(pCamara)
{
delete pCamara;
pCamara = NULL;
}
//videoInput::setComMultiThreaded(true); //如果项目中使用到不支持多线程的COM库,请不要执行此段代码
pCamara = (void*)(new videoInput());
((videoInput*)pCamara)->setIdealFramerate(nCamID,25); //设置当前采集帧率默认为25帧,实际可能大于25帧。
bool bRs = ((videoInput*)pCamara)->setupDevice(nCamID,nWidth,nHeight);
if(bRs)
{
m_ob = ob;
nDeviceId = nCamID;
((videoInput*)pCamara)->setUseCallback(true); //设置当前采集使用回调的方式获取数据
hThread = (void*)CreateThread(NULL,0,OnCaptureFrame,this,0,0);
m_bIsRun = true;
}
CCameraVI* pThis = (CCameraVI*)lpParam;
while(pThis->isRun())
{
if(pThis->isFrameNew())
{
const char* pData = (const char*)pThis->GetFrame();
pThis->getObserver()->onRGB(pData,pThis->GetWidth()*pThis->GetHeight()*3);
}
else
{
Sleep(1);
}
}
return 0;
整个采集逻辑就是这么简单的两段代码实现。
获取设备数量
videoInput::listDevices();
videoInput::listDevices();
strncpy(sName,videoInput::getDeviceName(nCamID),nBufferSize);
if (pCamara )
{
return ((videoInput*)pCamara)->getPixels(nDeviceId,false);
}