SkeyeLive中DirectShow采集音视频流程及几种采集方式介绍

前段时间SkeyeLive开放了DirectShow采集库,这个库底层采用DirectShow SDK的接口实现音视频的预览(播放)和采集;很多人可能还不太了解这个封装库的回调方式和之前的DShow线程采集方式有什么不同,或者说对DirectShow的采集流程还不太熟悉,下面我将就Windows平台下用使用DirectShow的过滤器(滤波器)进行流媒体开发的前端采集部分进行简要介绍,如果大家想深入的学习和探索,推荐大家去看看《Visual C++音频/视频处理技术及工程实践》这本书,第9章有详细的流程讲解。

一、枚举采集设备

使用采集设备前,需要首先确定系统已经安装的采集设备:视频、音频采集设备。系统设备枚举器为按类型枚举已注册在系统中的滤波器提供了统一的方法。而且它能够区分不同的硬件设备,即便是同一个滤波器支持它们。这对那些使用Windows驱动模型、KSProxy Filter的设备来说是非常有用的,系统设备枚举器对它们按不同的设备实例进行对待。

当利用系统设备枚举器查询设备的时候,系统设备枚举器为特定类型的设备(如音频捕获和视频压缩)生成了一张枚举表(Enumerator)。类型枚举器(Category Enumerator)为每个这种类型的设备返回一个Moniker,类型枚举器自动把每种即插即用的设备包含在内。

调用标准方法CoCreateInstance生成系统设备枚举器(Device Enumerator),类标识(CLSID)为CLSID_SystemDeviceEnum,方法如下:

CAMERA_LIST_T *CCameraDS::GetCameraList()
{
	if (NULL != cameraList.pCamera || cameraList.count > 0)
	{
		return &cameraList;
	}

	if (NULL == cameraList.pCamera)
	{
		cameraList.count = 0;

	   // enumerate all video capture devices
		CComPtr<ICreateDevEnum> pCreateDevEnum;
		HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
										IID_ICreateDevEnum, (void**)&pCreateDevEnum);

		CComPtr<IEnumMoniker> pEm;
		hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
		if (hr != NOERROR) 
		{
			return &cameraList;
		}

		pEm->Reset();

		CAMERA_INFO_T	*pCameraList = cameraList.pCamera;
		CAMERA_INFO_T	*pCameraInfo = NULL;

		ULONG cFetched;
		IMoniker *pM = NULL;
		while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
		{
			pCameraInfo = new CAMERA_INFO_T;
			memset(pCameraInfo, 0x00, sizeof(CAMERA_INFO_T));

            IPropertyBag *pBag=0;
            hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
            if(SUCCEEDED(hr))
            {
                VARIANT var;
                var.vt = VT_BSTR;
                hr = pBag->Read(L"FriendlyName", &var, NULL); //还有其他属性,像描述信息等等...
                if(hr == NOERROR)
                {
                    //获取设备名称
                    WideCharToMultiByte(CP_ACP,0,var.bstrVal,-1,pCameraInfo->friendlyName, sizeof(pCameraInfo->friendlyName) ,"",NULL);
                    SysFreeString(var.bstrVal);                
                }
                pBag->Release();
				cameraList.count++;

				{
					pCameraList = cameraList.pCamera;

					if (NULL == cameraList.pCamera)	cameraList.pCamera = pCameraInfo;
					else
					{
						while (NULL != pCameraList->pNext)
						{
							pCameraList = pCameraList->pNext;
						}
						pCameraList->pNext = pCameraInfo;
					}
				}
            }

			pM->Release();
		}

		pCreateDevEnum = NULL;
		pEm = NULL;
	}
	return &cameraList;
}

这是视频设备枚举部分,当然音频也是一样的,只需要把CreateClassEnumerator函数的第一个参数换成CLSID_AudioInputDeviceCategory即可。
注意:调用ICreateDevEnum::CreateClassEnumerator方法生成类型枚举器,参数为用户想要得到的类的ID(CLSID),该方法返回一个IEnumMoniker接口指针。如果指定的类型是空的或不存在,则函数ICreateDevEnum::CreateClassEnumerator将返回S_FALSE而不是错误代码,同时IEnumMoniker指针也是空的,这就要求我们在调用CreateClassEnumerator的时候明确用S_OK进行比较而不使用宏SUCCEEDED;(扯远了…)而在SkeyeLive中还提供了另外一种枚举音频设备的方式,那就是采用DirectSound的枚举方式:DirectSoundCaptureEnumerate这个函数来实现的,需要注意,这个函数枚举出的设备GUID有可能是空的,设备名称可能表象为”声卡主设备驱动“,经测试:这个设备是不能用于采集,也是不存在的,枚举过程中应该丢弃。当然,其实DirectShow也是封装了底层的DirectSound的接口来实现的COM接口的统一封装。
(需要重点说明的是:枚举设备这一块不是DShow封装库中的代码,这是由我们EasyDarwin团队的Gavin大神之前的DShow采集部分代码中提供的(前身是EasyCamera_win),我只是鸠占鹊巢的给大家讲解,向大神致敬~~~~~~~哈哈哈!)

二、使用Capture Graph Builder进行音视频采集

这个为了节约篇幅,本文以视频采集为例子进行讲解,其实音频采集是一模一样的(这就是封装的好处,不用关心底层的实现细节);
1、创建GraphBuilder
使用DirectShow进行视频采集,首先,创建视频捕获Graph,DShow SDK提供的是Graph Builder接口是IgraphBuilder。不过针对捕获任务(Capture),还有另一个接口ICaptureGraphBuilder2针对采集捕获的增强型接口,这个接口可以提供视频捕获预览窗口的创建和使用,然后,再创建一个媒体控制器对视频预览的播放进行控制,代码如下:

//创建视频捕获Graph
HRESULT CCaptureVideo::CreateCaptureGraphBuilder()
{
	HRESULT hr=NOERROR;
	
	if(m_pGraphBuilder==NULL)
	{
		hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGraphBuilder);
		if(FAILED(hr))
		{
			//ERR_DEBUG("CreateCaptureGraphBuilder Create m_pGraphBuilder Failed");
			return hr;
		}	
	}
	if(m_pCaptureGraphBulid==NULL)
	{
		 创建ICaptureGraphBuilder2接口,即创建视频捕获窗
		hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC,
							IID_ICaptureGraphBuilder2, (void **) &m_pCaptureGraphBulid);
		if (FAILED(hr))		
		{
			//ERR_DEBUG("CreateCaptureGraphBuilder CaptureGraphBuilder2 Failed");
			return hr;
		}	
		//将捕获窗的视频属性设为定义好的视频窗
		//给captureGrap builder指定一个图象filter,不能由于混合视频的呈现,video端口的管理
		hr = m_pCaptureGraphBulid->SetFiltergraph(m_pGraphBuilder);
	}

	//此处可能存在问题是否为一个链路就一个m_pMediaCon媒体控制//QueryInterface
	if(m_pMediaCon==NULL)
	{
		hr = m_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&m_pMediaCon);
		if (FAILED(hr))
		{
			//ERR_DEBUG("CreateCaptureGraphBuilder  QueryInterface m_pMediaCon Failed");
			return hr;
		}
	}

	if(m_pMediaEvent==NULL)
	{
		hr = m_pGraphBuilder->QueryInterface(IID_IMediaEvent, (void **) &m_pMediaEvent);
		if (FAILED(hr))
		{
			//ERR_DEBUG("CreateCaptureGraphBuilder  QueryInterface m_pMediaEvent Failed");
			return hr;
		}
	}

	return hr;
}

2、枚举设备并连接设备
枚举设备我们在上一节已经讲过了,这里直接查询到自己需要捕获的设备名称,然后绑定到过滤器上即可:

//枚举设备并绑定设备
BOOL CCaptureVideo::BindToVideoDev(int deviceId, IBaseFilter **pFilter)
{
	if (deviceId < 0)
	{
		return FALSE;
	}
	
	CComPtr<ICreateDevEnum> pCreateDevEnum;
	HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
									IID_ICreateDevEnum, (void**)&pCreateDevEnum);
	if (hr != NOERROR)
	{
		//ERR_DEBUG("Instance DeviceEnum Failed");
		return FALSE;
	}
	CComPtr<IEnumMoniker> pEm;
	hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0);
	if (hr != NOERROR)
	{
		//ERR_DEBUG("Enum VideoInputDeviceCategory Failed");
		return FALSE;
	}
	pEm->Reset();
	ULONG cFetched;
	IMoniker *pM=NULL;

	int index = 0;
	while((( pEm->Next(1, &pM, &cFetched))==S_OK)&&( index <= deviceId))
	{
		IPropertyBag *pBag=NULL;
		if (pM==NULL)
		{
			break;
		}
		hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
		if(pBag!=NULL) 
		{
			VARIANT var;
			var.vt = VT_BSTR;
			hr = pBag->Read(L"FriendlyName", &var, NULL);
			if (hr == NOERROR) 
			{
				if (index == deviceId)
				{
					//将视频设备绑定到基础过滤器上
					pM->BindToObject(0, 0, IID_IBaseFilter, (void**)pFilter);
				}
				SysFreeString(var.bstrVal);
				pBag->Release();
			}
		
		}
		pM->Release();
		index++;
	}
	return TRUE;
}

3、采集参数的设置

采集前需要对要采集的视频格式、图像质量进行设置,如视频的分辨率、帧率和数据格式,图像的亮度、色度和饱和度参数设置等。当然,我们这里只针对视频的宽高,帧率和数据格式进行了设置,如果大家还想进行更多的设置,可以使用OleCreatePropertyFrame函数以属性页的方式对视频属性和图像参数进行配置和修改。

HRESULT CCaptureVideo::SetVideoSize(int nPreview,CString strRGBBytes,int nFrameRate,int iWidth , int iHeight)
{
	HRESULT hr=E_FAIL;
	if(m_pCaptureGraphBulid==NULL)
		return hr;

	IAMStreamConfig *pAMStreamConfig=NULL;
	if(nPreview==0)
	{
		hr = m_pCaptureGraphBulid->FindInterface(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video,
				m_pBaseFilter,IID_IAMStreamConfig,(void **)&pAMStreamConfig);
	}
	else
	{
		hr = m_pCaptureGraphBulid->FindInterface(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,
				m_pBaseFilter,IID_IAMStreamConfig,(void **)&pAMStreamConfig);
	}

	if(FAILED( hr ))
	{
		SAFE_RELEASE(pAMStreamConfig);
		return hr;
	}
		//得到视频格式大小
	AM_MEDIA_TYPE *pmt;
	pAMStreamConfig->GetFormat(&pmt);

	//设置视频格式
	pmt->majortype = MEDIATYPE_Video;
	pmt->subtype  = GetMediaTypeGuid(strRGBBytes);


	VIDEOINFOHEADER *pvih = reinterpret_cast<VIDEOINFOHEADER *>(pmt->pbFormat);
	//设置回去
	int nDefualWidth = pvih->bmiHeader.biWidth;
	int nDefualHeight = pvih->bmiHeader.biHeight;

	pvih->bmiHeader.biWidth = iWidth; 
	pvih->bmiHeader.biHeight = iHeight;
	pvih->bmiHeader.biSizeImage = pmt->lSampleSize = iWidth*iHeight*pvih->bmiHeader.biPlanes*pvih->bmiHeader.biBitCount/8;
	pvih->AvgTimePerFrame = (LONGLONG)(10000000/nFrameRate);

	hr = pAMStreamConfig->SetFormat(pmt);
	if(FAILED(hr))
	{
		//如果设置失败可以选用默认的,但运用之后,小屏幕初始化时会出现闪动的情况		
		pvih->bmiHeader.biWidth = nDefualWidth; 
		pvih->bmiHeader.biHeight = nDefualHeight;
		pvih->bmiHeader.biSizeImage = pmt->lSampleSize = nDefualWidth*nDefualHeight*pvih->bmiHeader.biPlanes*pvih->bmiHeader.biBitCount/8;
		hr = pAMStreamConfig->SetFormat(pmt);
		
		if(FAILED(hr))
		{
			SAFE_RELEASE(pAMStreamConfig);
			FreeMediaType(*pmt);
			//ERR_DEBUG("初始化设置视频格式失败");
			return hr;
		}
	}
	SAFE_RELEASE(pAMStreamConfig);
	FreeMediaType(*pmt);//
	return hr;
}

4、建立视频渲染器
创建视频渲染器(呈现器),对捕获视频进行显示,代码如下:

HRESULT CCaptureVideo::CreateVideoRender(int nType)
{
	HRESULT hr = NOERROR;
	if(m_pWindowRender)
	{
		SAFE_RELEASE(m_pWindowRender);
	}
	//创建解码器filter,CLSID_VideoRendererDefault,//
	if(nType==0)
	{
		SAFE_RELEASE(m_pWindowRender);
		return NOERROR;
	}
	else if(nType==1)
	{
		
		hr = CoCreateInstance(CLSID_VideoRendererDefault,0,CLSCTX_INPROC_SERVER,
			IID_IBaseFilter,(void **)&m_pWindowRender);
		
	}
	else if(nType==2)
	{
		hr = CoCreateInstance(CLSID_VideoRenderer,0,CLSCTX_INPROC_SERVER,
			IID_IBaseFilter,(void **)&m_pWindowRender);
		
	}
	else if(nType==3)//不显示
	{
		hr = CoCreateInstance(CLSID_NullRenderer,0,CLSCTX_INPROC_SERVER,
			IID_IBaseFilter,(void **)&m_pWindowRender);
	}
	else
	{
		SAFE_RELEASE(m_pWindowRender);
		return NOERROR;
	}	
	
	if(FAILED(hr))
	{
		SAFE_RELEASE(m_pWindowRender);
		//ERR_DEBUG("接收创建呈现器失败");
		return hr; 
	}
	if(m_pGraphBuilder)
	{
		hr = m_pGraphBuilder->AddFilter(m_pWindowRender,L"recv render");
		if(FAILED(hr))
		{
			SAFE_RELEASE(m_pWindowRender);
			//ERR_DEBUG("加入接收创建呈现器失败");
			return hr; 
		}
	}
	return hr;
}

5、预览采集到的视频数据

首先,初始化过滤器链路管理器,把指定采集设备的过滤器添加到链路中,然后渲染RenderStream方法把所有的过滤器链接起来,最后根据设定的显示窗口预览采集到的视频数据,具体实现过程如下:

	hr = CreateCaptureSampleGrabber(m_strRGBByte);
	if(FAILED(hr))
	{
		SAFE_RELEASE(m_pSampleGrabberFilter);
		SAFE_RELEASE(m_pSampleGrabber);
		//ERR_DEBUG("CreateCaptureSampleGrabber Failed");
		return -1;
	}
	if(m_nDeinterlace==1)//m_iHeight/m_iWidth!=(1.5/4))
	{
		CreateDeinterlaceFilter();
	}
	if(m_pVideoDeinterlaceFilter)
	{
		hr = m_pCaptureGraphBulid->RenderStream(&pCategorySuc,&MEDIATYPE_Video,m_pBaseFilter,m_pVideoDeinterlaceFilter,m_pSampleGrabberFilter);
		hr = m_pCaptureGraphBulid->RenderStream(NULL,NULL,m_pSampleGrabberFilter,NULL,m_pWindowRender);
		if(FAILED(hr))
		{	
			hr = m_pCaptureGraphBulid->RenderStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video,m_pBaseFilter,m_pSampleGrabberFilter,NULL);
			if(FAILED(hr))
			{
				//ERR_DEBUG("PrivewVideoDev RenderStream Failed ");
				return -1;
			}
		}
	}
	else
	{
		hr = m_pCaptureGraphBulid->RenderStream(&pCategorySuc,&MEDIATYPE_Video,m_pBaseFilter,m_pSampleGrabberFilter,m_pWindowRender);
		if(FAILED(hr))
		{	
			hr = m_pCaptureGraphBulid->RenderStream(&pCategoryFail,&MEDIATYPE_Video,m_pBaseFilter,m_pSampleGrabberFilter,m_pWindowRender);
			if(FAILED(hr))
			{
				//ERR_DEBUG("PrivewVideoDev RenderStream Failed ");
				return -1;
			}
		}
	}
	
	if(m_bThread)
	{
		m_pSampleGrabber->SetOneShot(FALSE);
		m_pSampleGrabber->SetBufferSamples(TRUE);
	//	m_pSampleGrabber->SetOneShot(TRUE);

	}
	else
	{
		m_pSampleGrabber->SetBufferSamples(TRUE);
		m_pSampleGrabber->SetOneShot(FALSE);
		//m_nDataType:数据类型1--音频,2--视频数据
		//nIndex:设备编号:音频-1,视频0---N
		m_cSampleGrabberCB.SetDataInfo(m_nIndex, m_nDataType);
		int nMode=1;//0--SampleCB,1--BufferCB
		m_pSampleGrabber->SetCallback(&m_cSampleGrabberCB, nMode);
	}

	if(m_pVideoWin==NULL&&m_nRenderType!=2)
	{
		hr = m_pCaptureGraphBulid->FindInterface(&pCategorySuc,&MEDIATYPE_Video,//CAPTURE
							m_pBaseFilter,IID_IVideoWindow,(void **)&m_pVideoWin);
		if (FAILED(hr))//建立失败则查找CAPTURE口
		{
			hr = m_pCaptureGraphBulid->FindInterface(&pCategoryFail,&MEDIATYPE_Video,//CAPTURE
							m_pBaseFilter,IID_IVideoWindow,(void **)&m_pVideoWin);
			if (FAILED(hr))
			{
				//ERR_DEBUG("CreateCaptureGraphBuilder  QueryInterface m_pVideoWin Failed");
				return -1;
			}
		}
	}
	
	//设置视频显示窗口
	SetupVideoWindow();
	hr = StartPreview();
	if(FAILED(hr))
	{
		return -1;
	}

然后,设置视频显示窗口:

//设置窗口句柄,并自动更新显示大小
HRESULT CCaptureVideo::SetupVideoWindow(HWND hWnd)
{
	HRESULT hr=NOERROR;

	if(m_pVideoWin==NULL)
	{
		return hr;
	}

	if(hWnd!=NULL)
		m_hWnd = hWnd;

	hr = m_pVideoWin->put_Owner((OAHWND)m_hWnd);
//	hr = m_pVideoWin->put_MessageDrain((OAHWND)m_hWnd);
	if (FAILED(hr))
	{
		//ERR_DEBUG("SetupVideoWindow put_Owner Error");
		return hr;
	}
	hr = m_pVideoWin->put_WindowStyle(WS_CHILD |SS_NOTIFY| WS_CLIPCHILDREN);
	if (FAILED(hr))
	{
		//ERR_DEBUG("SetupVideoWindow put_WindowStyle");
		return hr;
	}

	ResizeVideoWindow();
	hr = m_pVideoWin->put_Visible(OATRUE);

	return hr;
}

最后,开始预览:

HRESULT CCaptureVideo::StartPreview()
{
	HRESULT hr=NOERROR;
	if(m_pMediaCon==NULL)
	{
		hr = m_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&m_pMediaCon);
		if(SUCCEEDED(hr))
		{
			hr = m_pMediaCon->Run();
			if(FAILED(hr))
			{
				m_pMediaCon->Stop();
			}
		}
	}
	else
	{
		hr = m_pMediaCon->Run();
		if(FAILED(hr))
		{
			m_pMediaCon->Stop();
		}
	}
	return hr;
}

至此,DShow对视频的采集整个完整的流程就完成了。

三、DShow采集的两种模式
1、线程模式(拉模式)
线程模式采用多线程的方式,在线程回调中调用GetCurrentBuffer函数获取采集缓存中的一帧数据,这里获取的数据是之前设置的色彩格式的数据(如果设置成功的话,否则是默认格式);如果要得到指定的格式需要进行色彩格式转换。
线程执行函数如下:

void CCaptureVideo::OnThreadDeal()
{
	BYTE *pData=NULL;
	long lDatasize=0;
	char strMediaType[24]=_T("YUY2");
	if(!m_strRGBByte.IsEmpty())
	{
		strcpy_s(strMediaType, 24, m_strRGBByte);
	}
	//读取缓冲区数据
	pData=GetCurrentBuffer(lDatasize,strMediaType);
	//TRACE("OnThreadDeal:%d\r\n",lDatasize);
	if (m_sThreadCalbackInfo.realDataCalFunc&&m_sThreadCalbackInfo.pMaster&&pData&&lDatasize>0)
	{
		//执行数据回调
		m_sThreadCalbackInfo.realDataCalFunc(m_nIndex, pData, lDatasize, 
			(RealDataStreamType)m_nDataType,NULL, m_sThreadCalbackInfo.pMaster);
	}
}

2、回调模式(推模式)
回调模式是利用ISampleGrabber提供的回调函数接口进行设置,该设置回调函数原型如下:

        virtual HRESULT STDMETHODCALLTYPE ISampleGrabber::SetCallback( 
            ISampleGrabberCB *pCallback,
            long WhichMethodToCallback) = 0;

从函数接口我们可以看出,第一个参数需要一个ISampleGrabberCB类型的参数,我们自定义一个回调接口类:class CSampleGrabberCB:public ISampleGrabberCB,用以处理回调数据的接收及外调;
设置接口:

/*
nDataType:数据类型1--音频,2--视频数据
nIndex:设备编号:音频-1,视频0---N
*/
void  CSampleGrabberCB::SetDataInfo(int nIndex,int nDataType)
{
	m_nIndex=nIndex;
	m_nDataType= nDataType;
}

回调函数处理:

//统一的回调函数
STDMETHODIMP   CSampleGrabberCB::BufferCB( double dblSampleTime, BYTE *pBuffer, long lBufferSize )
{

	if   (!pBuffer) 
		return   E_POINTER; 

	now_tick=::GetTickCount();

	first_tick = now_tick;
	if (m_nDataType == 1)//视频
	{
 //		TRACE("BufferCB:%d-%d-%d-%d\r\n", m_nIndex, m_nDataType, now_tick - first_tick, lBufferSize);
	}
	else if (m_nDataType == 2)//音频
	{
//		TRACE("BufferCB:%d-%d-%d-%d\r\n", m_nIndex, m_nDataType, now_tick - first_tick, lBufferSize);
	}

	if(pBuffer==NULL||lBufferSize<=0)
		return 0;

	if (m_realDataCallback && m_pMaster)
	{
		m_realDataCallback(m_nIndex, pBuffer, lBufferSize, (RealDataStreamType)m_nDataType,NULL, m_pMaster);
		return 0;
	}
	if(   g_cbInfo.lBufferSize   <   lBufferSize   ) 
	{ 
		delete   []   g_cbInfo.pBuffer; 
		g_cbInfo.pBuffer   =   NULL; 
		g_cbInfo.lBufferSize   =   0; 
		g_cbInfo.bHaveData=FALSE;
	} 

	//   Since   we   can 't   access   Windows   API   functions   in   this   callback,   just 
	//   copy   the   bitmap   data   to   a   global   structure   for   later   reference. 
	g_cbInfo.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   (!g_cbInfo.pBuffer) 
	{ 
		g_cbInfo.pBuffer   =   new   BYTE[lBufferSize]; 
		g_cbInfo.lBufferSize   =   lBufferSize; 
	} 

	if(   !g_cbInfo.pBuffer   ) 
	{ 
		g_cbInfo.lBufferSize   =   0;
		g_cbInfo.bHaveData=FALSE;
		return   E_OUTOFMEMORY; 
	} 

	//   Copy   the   bitmap   data   into   our   global   buffer 
	memcpy(g_cbInfo.pBuffer,   pBuffer,   lBufferSize);
	g_cbInfo.bHaveData=TRUE;

	return   0;
}

两种方式相比较,个人观点:各有优劣;在SkeyeLive中我们采用的是回调方式,当时引进这个库就是为了能在采集端保证音视频从源头是同步的,当然,其实线程模式也是能实现同步的;线程模式的优点是:采集即时性高,即需即取,几乎不会有延时,缺点就是:如果出现取数据端不及时时,如果不考虑缓存的情况下可能就会出现丢帧。而回调模式就正好相反,其优点是:稳定性高,随时都能保证取的帧是连续的,即使不做缓存也不会出现取出来的数据出现丢帧的情况,当然在取数据时比如编码慢(或者回调中做其他延时处理),就会出现预览和回调同步延时的情况,回调缓存的数据量会越来越大,延时也将增大;当然,如果在多路同时采集时,甚至多路同时进行数据处理时,采用回调模式会更显优势!

  • 4
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:岁月 设计师:pinMode 返回首页
评论 1

打赏作者

SwordTwelve

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值