[Windows Mobile Software Development] xsl:
Questions

As mentioned in the text below the TakePicture() method is not working properly on the HTC HD 2 device. It would be nice if someone could look at the code below and tell me if it is right or wrong what I'm doing.

Introduction

I recently asked a question on another forum about displaying a video preview and taking camera image with DirectShow. The tricky thing about the topic is, that it's very hard to find good examples and the documentation and the framework itself is very hard to understand for someone who is new to windows programming and C++ in general.

Nevertheless I managed to create a class that implements most of this features and probably works with most mobile devices. Probably because as far as I know the DirectShow implementation depends a lot on the device itself. I could only test it with the HTC HD and HTC HD2, which are known as quite incompatible.

HTC HD

- Working: Video preview, writing photo to file

- Not working: Set video resolution (CRASH), set photo resolution (LOW quality)

HTC HD 2

- Working: Set video resolution, set photo resolution

- Problematic: Video Preview rotated

- Not working: Writing photo to file

To make it easier for others by providing a working example, I decided to share everything I have got so far below. I removed all of the error handling for the sake of simplicity. As far as documentation goes, I can recommend you to read the MSDN documentation about the topic. After that the code below is pretty straight forward.

void Camera::Init()
{
CreateComObjects();

_captureGraphBuilder->SetFiltergraph(_filterGraph);

InitializeVideoFilter();
InitializeStillImageFilter();
}

Dipslay a video preview (working with any tested handheld):

void Camera::DisplayVideoPreview(HWND windowHandle)
{
IVideoWindow *_vidWin;

_filterGraph->QueryInterface(IID_IMediaControl,(void **) &_mediaControl);
_filterGraph->QueryInterface(IID_IVideoWindow, (void **) &_vidWin);
_videoCaptureFilter->QueryInterface(IID_IAMVideoControl,
(void**) &_videoControl);

_captureGraphBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, _videoCaptureFilter, NULL, NULL);

CRect rect;
long width, height;

GetClientRect(windowHandle, &rect);

_vidWin->put_Owner((OAHWND)windowHandle);
_vidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);

_vidWin->get_Width(&width);
_vidWin->get_Height(&height);
height = rect.Height();

_vidWin->put_Height(height);
_vidWin->put_Width(rect.Width());
_vidWin->SetWindowPosition(0,0, rect.Width(), height);

_mediaControl->Run();
}

HTC HD2: If set SetPhotoResolution() is called FindPin will return E_FAIL. If not, it will create a file full of null bytes. HTC HD: Works

void Camera::TakePicture(WCHAR *fileName)
{
CComPtr<IFileSinkFilter> fileSink;
CComPtr<IPin> stillPin;
CComPtr<IUnknown> unknownCaptureFilter;
CComPtr<IAMVideoControl> videoControl;

_imageSinkFilter.QueryInterface(&fileSink);
fileSink->SetFileName(fileName, NULL);

_videoCaptureFilter.QueryInterface(&unknownCaptureFilter);

_captureGraphBuilder->FindPin(unknownCaptureFilter, PINDIR_OUTPUT,
&PIN_CATEGORY_STILL, &MEDIATYPE_Video, FALSE, 0, &stillPin);

_videoCaptureFilter.QueryInterface(&videoControl);
videoControl->SetMode(stillPin, VideoControlFlag_Trigger);
}

Set resolution: Works great on HTC HD2. HTC HD won't allow SetVideoResolution() and only offers one low resolution photo resolution:

void Camera::SetVideoResolution(int width, int height)
{
SetResolution(true, width, height);
}

void Camera::SetPhotoResolution(int width, int height)
{
SetResolution(false, width, height);
}

void Camera::SetResolution(bool video, int width, int height)
{
IAMStreamConfig *config;
config = NULL;

if (video)
{
_captureGraphBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, _videoCaptureFilter, IID_IAMStreamConfig,
(void**) &config);
}
else
{
_captureGraphBuilder->FindInterface(&PIN_CATEGORY_STILL,
&MEDIATYPE_Video, _videoCaptureFilter, IID_IAMStreamConfig,
(void**) &config);

}

int resolutions, size;
VIDEO_STREAM_CONFIG_CAPS caps;
config->GetNumberOfCapabilities(&resolutions, &size);

for (int i = 0; i < resolutions; i++)
{
AM_MEDIA_TYPE *mediaType;
if (config->GetStreamCaps(i, &mediaType,
reinterpret_cast<BYTE*>(&caps)) == S_OK )
{
int maxWidth = caps.MaxOutputSize.cx;
int maxHeigth = caps.MaxOutputSize.cy;

if(maxWidth == width && maxHeigth == height)
{
VIDEOINFOHEADER *info =
reinterpret_cast<VIDEOINFOHEADER*>(mediaType->pbFormat);

info->bmiHeader.biWidth = maxWidth;
info->bmiHeader.biHeight = maxHeigth;
info->bmiHeader.biSizeImage = DIBSIZE(info->bmiHeader);
config->SetFormat(mediaType);

DeleteMediaType(mediaType);
break;

}

DeleteMediaType(mediaType);
}
}
}

Other methods used to build the filter graph and create the COM objects:

v

oid Camera::CreateComObjects()
{
CoInitialize(NULL);

CoCreateInstance(CLSID_CaptureGraphBuilder, NULL, CLSCTX_INPROC_SERVER,
IID_ICaptureGraphBuilder2, (void **) &_captureGraphBuilder);

CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **) &_filterGraph);

CoCreateInstance(CLSID_VideoCapture, NULL, CLSCTX_INPROC,
IID_IBaseFilter, (void**) &_videoCaptureFilter);

CoCreateInstance(CLSID_IMGSinkFilter, NULL, CLSCTX_INPROC,
IID_IBaseFilter, (void**) &_imageSinkFilter);
}

void Camera::InitializeVideoFilter()
{
_videoCaptureFilter->QueryInterface(&_propertyBag);

wchar_t deviceName[MAX_PATH] = L"\0";
GetDeviceName(deviceName);
CComVariant comName = deviceName;

CPropertyBag propertyBag;
propertyBag.Write(L"VCapName", &comName);
_propertyBag->Load(&propertyBag, NULL);

_filterGraph->AddFilter(_videoCaptureFilter,
L"Video Capture Filter Source");
}

void Camera::InitializeStillImageFilter()
{
_filterGraph->AddFilter(_imageSinkFilter, L"Still image filter");

_captureGraphBuilder->RenderStream(&PIN_CATEGORY_STILL,
&MEDIATYPE_Video, _videoCaptureFilter, NULL, _imageSinkFilter);
}

void Camera::GetDeviceName(WCHAR *deviceName)
{
HRESULT hr = S_OK;
HANDLE handle = NULL;
DEVMGR_DEVICE_INFORMATION di;
GUID guidCamera = { 0xCB998A05, 0x122C, 0x4166, 0x84, 0x6A, 0x93, 0x3E,
0x4D, 0x7E, 0x3C, 0x86 };

di.dwSize = sizeof(di);

handle = FindFirstDevice(DeviceSearchByGuid, &guidCamera, &di);
StringCchCopy(deviceName, MAX_PATH, di.szLegacyName);
}

Full header file:

#ifndef __CAMERA_H__
#define __CAMERA_H__

class Camera
{
public:
void Init();
void DisplayVideoPreview(HWND windowHandle);
void TakePicture(WCHAR *fileName);
void SetVideoResolution(int width, int height);
void SetPhotoResolution(int width, int height);

private:
CComPtr<ICaptureGraphBuilder2> _captureGraphBuilder;
CComPtr<IGraphBuilder> _filterGraph;
CComPtr<IBaseFilter> _videoCaptureFilter;
CComPtr<IPersistPropertyBag> _propertyBag;
CComPtr<IMediaControl> _mediaControl;
CComPtr<IAMVideoControl> _videoControl;
CComPtr<IBaseFilter> _imageSinkFilter;

void GetDeviceName(WCHAR *deviceName);
void InitializeVideoFilter();
void InitializeStillImageFilter();
void CreateComObjects();
void SetResolution(bool video, int width, int height);
};

#endif