由于3G手机应用视频电话的时候要双camera,那么就存在切换的问题,我就在开发板上进行了调试。marvell的zylonite开发板Windows Mobile6.0 BSP关于camera的驱动变化还是很明显的,采用了wince6.0的camera驱动。
找了半天才在微软的MSDN上看到了各个pdd函数的说明。整个设计的原理就是APP先停止preview,发出切换sensor的指令,驱动响应,把sensor相关的都换成另外一个sensor,然后再重新preview。
首先camera的应用程序得自己写,不然双camera的切换无法在应用上修改,于是找了个camera的应用代码参考了下,居然都编译不过,晕,我不是做app的,但是又没有人帮我,只好硬着头皮看资料看代码。幸好自己也做过一点PC上的应用程序,终于看懂,原来采用WTL+directshow写的camera应用,mainframe+manager+ view+setting结构很清晰。应用程序写上如下代码:
BOOL CCameraManager::CameraSwitchSensor(CAMERA_SWITCH sensor)
...
{
LRESULT hr;
CComPtr<IAMCameraControl> pCameraControl;
long lCurrentVal,lCurrentFlags;
long CameraControl_Switch=CameraControl_Flash+0xff;
if(sensor>=SENSOR_MAX)
return FALSE;
CHK(m_pVideoCapFilter->QueryInterface(&pCameraControl));
CHK(pCameraControl->Get(CameraControl_Switch, &lCurrentVal, &lCurrentFlags));
if(lCurrentVal == sensor)
...{
//return TRUE;
}
CHK(pCameraControl->Set(CameraControl_Switch, sensor, CameraControl_Flags_Manual));
err_exit:
if( FAILED( hr ))
...{
CCameraParam::GetCameraParam()->GetErrorlog()->WriteLog(L"Error: CCameraManager::CameraSwitchSensor");
return FALSE;
}
return TRUE;
}
红色字体为关键,由于通过dshow才控制camera,所以得找到dshow动作对应于驱动的接口,参考wm的文档并调试,发现这个可以控制camera,会调用CAM_IOControl,Ioctl=IOCTL_CS_PROPERTY,
EXTERN_C
BOOL
CAM_IOControl(
DWORD dwContext,
DWORD Ioctl,
UCHAR
*
pInBufUnmapped,
DWORD InBufLen,
UCHAR
*
pOutBufUnmapped,
DWORD OutBufLen,
DWORD
*
pdwBytesTransferred
)
...
{
DEBUGMSG( ZONE_FUNCTION, ( _T("CAM_IOControl(%08x): IOCTL:0x%x, InBuf:0x%x, InBufLen:%d, OutBuf:0x%x, OutBufLen:0x%x) "), dwContext, Ioctl, pInBufUnmapped, InBufLen, pOutBufUnmapped, OutBufLen ) );
//NKDbgPrintfW(L"CAM_IOControl 0x%x", Ioctl);
UCHAR * pInBuf = NULL;
UCHAR * pOutBuf = NULL;
DWORD dwErr = ERROR_INVALID_PARAMETER;
BOOL bRc = FALSE;
if ( ( NULL == pInBufUnmapped )
|| ( InBufLen < sizeof ( CSPROPERTY ) )
|| ( NULL == pdwBytesTransferred ) )
...{
SetLastError( dwErr );
return bRc;
}
//All buffer accesses need to be protected by try/except
pInBuf = pInBufUnmapped;
pOutBuf = pOutBufUnmapped;
CAMERAOPENHANDLE * pCamOpenHandle = reinterpret_cast<CAMERAOPENHANDLE *>( dwContext );
CAMERADEVICE * pCamDevice = pCamOpenHandle->pCamDevice;
CSPROPERTY * pCsProp = reinterpret_cast<CSPROPERTY *>(pInBuf);
if ( NULL == pCsProp )
...{
DEBUGMSG( ZONE_IOCTL|ZONE_ERROR, (_T("CAM_IOControl(%08x): Invalid Parameter. "), dwContext ) );
return dwErr;
}
switch ( Ioctl )
...{
// Power Management Support.
case IOCTL_POWER_CAPABILITIES:
case IOCTL_POWER_QUERY:
case IOCTL_POWER_SET:
case IOCTL_POWER_GET:
...{
NKDbgPrintfW(L"camera IOCTL_POWER+");
DEBUGMSG( ZONE_IOCTL, ( _T("CAM_IOControl(%08x): Power Management IOCTL "), dwContext ) );
__try
...{
dwErr = pCamDevice->AdapterHandlePowerRequests(Ioctl, pInBuf, InBufLen, pOutBuf, OutBufLen, pdwBytesTransferred );
}
__except ( EXCEPTION_EXECUTE_HANDLER )
...{
DEBUGMSG( ZONE_IOCTL, ( _T("CAM_IOControl(%08x):Exception in Power Management IOCTL"), dwContext ) );
}
break;
}
case IOCTL_CS_PROPERTY:
...{
DEBUGMSG( ZONE_IOCTL, ( _T("CAM_IOControl(%08x): IOCTL_CS_PROPERTY "), dwContext ) );
__try
...{
dwErr = pCamDevice->AdapterHandleCustomRequests( pInBuf,InBufLen, pOutBuf, OutBufLen, pdwBytesTransferred );
if ( ERROR_NOT_SUPPORTED == dwErr )
...{
if ( TRUE == IsEqualGUID( pCsProp->Set, CSPROPSETID_Pin ) )
...{
dwErr = pCamDevice->AdapterHandlePinRequests( pInBuf, InBufLen, pOutBuf, OutBufLen, pdwBytesTransferred );
}
else if ( TRUE == IsEqualGUID( pCsProp->Set, CSPROPSETID_VERSION ) )
...{
dwErr = pCamDevice->AdapterHandleVersion( pOutBuf, OutBufLen, pdwBytesTransferred );
}
else if ( TRUE == IsEqualGUID( pCsProp->Set, PROPSETID_VIDCAP_VIDEOPROCAMP ) )
...{
dwErr = pCamDevice->AdapterHandleVidProcAmpRequests( pInBuf,InBufLen, pOutBuf, OutBufLen, pdwBytesTransferred );
}
else if ( TRUE == IsEqualGUID( pCsProp->Set, PROPSETID_VIDCAP_CAMERACONTROL ) )
...{
dwErr = pCamDevice->AdapterHandleCamControlRequests( pInBuf,InBufLen, pOutBuf, OutBufLen, pdwBytesTransferred );
}
else if ( TRUE == IsEqualGUID( pCsProp->Set, PROPSETID_VIDCAP_VIDEOCONTROL ) )
...{
dwErr = pCamDevice->AdapterHandleVideoControlRequests( pInBuf,InBufLen, pOutBuf, OutBufLen, pdwBytesTransferred );
}
else if ( TRUE == IsEqualGUID( pCsProp->Set, PROPSETID_VIDCAP_DROPPEDFRAMES) )
...{
dwErr = pCamDevice->AdapterHandleDroppedFramesRequests( pInBuf,InBufLen, pOutBuf, OutBufLen, pdwBytesTransferred );
}
}
}
__except ( EXCEPTION_EXECUTE_HANDLER )
...{
DEBUGMSG( ZONE_IOCTL, ( _T("CAM_IOControl(%08x):Exception in IOCTL_CS_PROPERTY"), dwContext ) );
}
break;
}
default:
...{
DEBUGMSG( ZONE_IOCTL, (_T("CAM_IOControl(%08x): Unsupported IOCTL code %u "), dwContext, Ioctl ) );
dwErr = ERROR_NOT_SUPPORTED;
break;
}
}
// pass back appropriate response codes
SetLastError( dwErr );
return ( ( dwErr == ERROR_SUCCESS ) ? TRUE : FALSE );
}
哈哈,红色字体就是
IAMCameraControl的set接口对应于driver会被调用的地方,由于这里还是driver的mdd层,所以尽量不改,于是放到pdd层来实现,很自然的想到
AdapterHandleCustomRequests,该函数会调用pdd的DWORD CCameraPdd::HandleAdapterCustomProperties( PUCHAR pInBuf, DWORD InBufLen, PUCHAR pOutBuf, DWORD OutBufLen, PDWORD pdwBytesTransferred ),本来这函数是空函数,结果这里就可以放入双camera的切换代码了(不知道是微软写的还是marvell写的,想的真周到,以前还没有的,现在就有了 ,赞一个),实现如下:
DWORD CCameraPdd::HandleAdapterCustomProperties( PUCHAR pInBuf, DWORD InBufLen, PUCHAR pOutBuf, DWORD OutBufLen, PDWORD pdwBytesTransferred )
...
{
typedef enum
...{
SENSOR_FRONT,
SENSOR_REAR,
SENSOR_MAX,
}CAMERA_SWITCH;
CONST LONG CAMERACONTROL_SWITCH = CSPROPERTY_CAMERACONTROL_FLASH+0XFF;
PCSPROPERTY_CAMERACONTROL_S pCsPropCamControlInput = NULL;
CSPROPERTY * pCsProp = reinterpret_cast<CSPROPERTY *>(pInBuf);
if( ( TRUE == IsEqualGUID( pCsProp->Set, PROPSETID_VIDCAP_CAMERACONTROL ) )
&& (pCsProp->Id == CAMERACONTROL_SWITCH))
...{
pCsPropCamControlInput = reinterpret_cast<PCSPROPERTY_CAMERACONTROL_S>(pInBuf);
switch(pCsProp->Flags)
...{
case CSPROPERTY_TYPE_GET:
break;
case CSPROPERTY_TYPE_SET:
if(pCsPropCamControlInput->Value == SENSOR_FRONT)
...{
sensor=sensor_ov7660;
set_video_Format(m_pModeVideoFormat, sensor);
NKDbgPrintfW(L"sensor=sensor_ov7660; ");
}
else if((pCsPropCamControlInput->Value == SENSOR_REAR))
...{
sensor=sensor_ov2630;
set_video_Format(m_pModeVideoFormat, sensor);
NKDbgPrintfW(L"sensor=sensor_ov2630; ");
}
break;
default:
break;
}
return ERROR_SUCCESS;
}
else
...{
DEBUGMSG( ZONE_IOCTL, ( _T("IOControl Adapter PDD: Unsupported PropertySet Request ")));
return ERROR_NOT_SUPPORTED;
}
}
这里都是要自己写的啦,太多了就不讲了,不过函数
set_video_Format要讲一下,这个函数很重要,调试时发现不是dma没有,就是应用获取的格式不对,这个函数的实现就是为了双camera切换专门定做的
DWORD CCameraPdd::set_video_Format(PPINVIDEOFORMAT pModeVideoFormat, Sensor
*
sensor)
...
{
if(NULL==pModeVideoFormat || NULL==sensor)
return ERROR_INVALID_TARGET_HANDLE;
for (ULONG i = 0; i < m_ulCTypes; i++)
...{
unsigned int nformats;
PCS_DATARANGE_VIDEO** formats;
formats = &pModeVideoFormat[i].pCsDataRangeVideo;
nformats = sensor->get_formats(i, formats);
pModeVideoFormat[i].ulAvailFormats = nformats;
if ( NULL == pModeVideoFormat[i].pCsDataRangeVideo )
return ERROR_INSUFFICIENT_BUFFER;
}
for (ULONG i = 0; i < m_ulCTypes; i++)
...{
for (ULONG j = 0; j < pModeVideoFormat[i].ulAvailFormats; j++)
...{
PCS_VIDEOINFOHEADER pCsVideoInfoHdr;
pCsVideoInfoHdr = &pModeVideoFormat[i].pCsDataRangeVideo[j]->VideoInfoHeader;
camera_cfg_t* cfg = sensor->get_camera_cfg(pCsVideoInfoHdr, i);
if (!cfg)
NKDbgPrintfW(L"failed to get camera configuration!!!");
sensor->init_camera_cfg(cfg, i);
qci_notify_formats(&cfg->qci_format);
}
}
return ERROR_SUCCESS;
}
这样不同sensor对应于QCI的DMA buf也有了,APP preview的格式也与不同的sensor对应起来了很好,而且还不会多分配buf。
CComPtr
<
IAMStreamConfig
>
pStremConfig;
CHK(m_pCapGraphBuilder
->
FindInterface(
&
PIN_CATEGORY_CAPTURE,
0
, m_pVideoCapFilter, IID_IAMStreamConfig, (
void
**
)
&
pStremConfig))
CHK(pStremConfig
->
GetNumberOfCapabilities(
&
iCount,
&
iSize));
if
(iSize
==
sizeof
(VIDEO_STREAM_CONFIG_CAPS))
...
{
for (int iFormat = 0; iFormat < iCount; iFormat++)
...{
VIDEO_STREAM_CONFIG_CAPS scc;
AM_MEDIA_TYPE *pmtConfig;
CHK(pStremConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc));
if (SUCCEEDED(hr))
...{
if(scc.MinOutputSize.cx == pSize->cx && scc.MinOutputSize.cy == pSize->cy)
...{
CHK(pStremConfig->SetFormat(pmtConfig));
DeleteMediaType(pmtConfig);
break;
}
DeleteMediaType(pmtConfig);
}
}
}
上面红色代码终于得到正确的结果了,于是添加app代码
LRESULT CMainFrame::OnSwitchsensorFront(WORD
/**/
/*wNotifyCode*/
, WORD
/**/
/*wID*/
, HWND
/**/
/*hWndCtl*/
, BOOL
&
/**/
/*bHandled*/
)
...
{
// TODO: Add your command handler code here
m_view.StopGraph();
m_view.CameraSwitchSensor(SENSOR_FRONT);
m_view.SaveElectricity(TRUE);
m_view.StartupCameraMode(m_view.GetCurrentCameraMode());
m_view.RunGraph();
m_view.RedrawWindow();
return 0;
}
LRESULT CMainFrame::OnSwitchsensorRear(WORD
/**/
/*wNotifyCode*/
, WORD
/**/
/*wID*/
, HWND
/**/
/*hWndCtl*/
, BOOL
&
/**/
/*bHandled*/
)
...
{
// TODO: Add your command handler code here
m_view.StopGraph();
m_view.CameraSwitchSensor(SENSOR_REAR);
m_view.SaveElectricity(TRUE);
m_view.StartupCameraMode(m_view.GetCurrentCameraMode());
m_view.RunGraph();
m_view.RedrawWindow();
return 0;
}
bsp和APP一起调试一下通过。
这是最后的结果,过程做了很多调试,发现只有这些不能去掉。在添加一个切换的菜单时,发现居然出现不了,原来是多语言包的问题,资源和程序是分开的,用0804.MUI。在.rc文件添加
/**/
/////////////////////////////////////////////////////////////////////////////
//
//
Menu
//
IDR_MAINFRAME MENU
BEGIN
MENUITEM
"
拍摄
"
, IDB_CAPTUREORSTOP
POPUP
"
菜单
"
BEGIN
POPUP
"切换摄像头"
BEGIN
MENUITEM "前置", ID_SWITCHSENSOR_FRONT
MENUITEM "后置"
, ID_SWITCHSENSOR_REAR
END
END
END
编译后放到exe文件同一个目录下搞定。
还有很多bsp和directshow的细节问题太多,就不写了,主要是说明下思路,现在还有个问题就是切换的太慢了,要5-9秒钟,有空看看瓶颈在哪里,优化下看看。去掉了些调试功能,好像快了一个2秒一个4秒。