579 lines
16 KiB
C++
579 lines
16 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
//------------------------------------------------------------------------------
|
|
// File: PlayCap.cpp
|
|
//
|
|
// Desc: DirectShow sample code - a very basic application using video capture
|
|
// devices. It creates a window and uses the first available capture
|
|
// device to render and preview video capture data.
|
|
//------------------------------------------------------------------------------
|
|
|
|
#define _WIN32_WINNT 0x0500
|
|
|
|
#include <windows.h>
|
|
#include <dshow.h>
|
|
#include <stdio.h>
|
|
#include <strsafe.h>
|
|
|
|
#include "PlayCap.h"
|
|
|
|
// An application can advertise the existence of its filter graph
|
|
// by registering the graph with a global Running Object Table (ROT).
|
|
// The GraphEdit application can detect and remotely view the running
|
|
// filter graph, allowing you to 'spy' on the graph with GraphEdit.
|
|
//
|
|
// To enable registration in this sample, define REGISTER_FILTERGRAPH.
|
|
//
|
|
#define REGISTER_FILTERGRAPH
|
|
|
|
|
|
//
|
|
// Global data
|
|
//
|
|
HWND ghApp=0;
|
|
DWORD g_dwGraphRegister=0;
|
|
|
|
IVideoWindow * g_pVW = NULL;
|
|
IMediaControl * g_pMC = NULL;
|
|
IMediaEventEx * g_pME = NULL;
|
|
IGraphBuilder * g_pGraph = NULL;
|
|
ICaptureGraphBuilder2 * g_pCapture = NULL;
|
|
PLAYSTATE g_psCurrent = Stopped;
|
|
|
|
|
|
HRESULT CaptureVideo()
|
|
{
|
|
HRESULT hr;
|
|
IBaseFilter *pSrcFilter=NULL;
|
|
|
|
// Get DirectShow interfaces
|
|
hr = GetInterfaces();
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Failed to get video interfaces! hr=0x%x"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Attach the filter graph to the capture graph
|
|
hr = g_pCapture->SetFiltergraph(g_pGraph);
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Failed to set capture filter graph! hr=0x%x"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Use the system device enumerator and class enumerator to find
|
|
// a video capture/preview device, such as a desktop USB video camera.
|
|
hr = FindCaptureDevice(&pSrcFilter);
|
|
if (FAILED(hr))
|
|
{
|
|
// Don't display a message because FindCaptureDevice will handle it
|
|
return hr;
|
|
}
|
|
|
|
// Add Capture filter to our graph.
|
|
hr = g_pGraph->AddFilter(pSrcFilter, L"Video Capture");
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Couldn't add the capture filter to the graph! hr=0x%x\r\n\r\n")
|
|
TEXT("If you have a working video capture device, please make sure\r\n")
|
|
TEXT("that it is connected and is not being used by another application.\r\n\r\n")
|
|
TEXT("The sample will now close."), hr);
|
|
pSrcFilter->Release();
|
|
return hr;
|
|
}
|
|
|
|
// Render the preview pin on the video capture filter
|
|
// Use this instead of g_pGraph->RenderFile
|
|
hr = g_pCapture->RenderStream (&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
|
|
pSrcFilter, NULL, NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Couldn't render the video capture stream. hr=0x%x\r\n")
|
|
TEXT("The capture device may already be in use by another application.\r\n\r\n")
|
|
TEXT("The sample will now close."), hr);
|
|
pSrcFilter->Release();
|
|
return hr;
|
|
}
|
|
|
|
// Now that the filter has been added to the graph and we have
|
|
// rendered its stream, we can release this reference to the filter.
|
|
pSrcFilter->Release();
|
|
|
|
// Set video window style and position
|
|
hr = SetupVideoWindow();
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Couldn't initialize video window! hr=0x%x"), hr);
|
|
return hr;
|
|
}
|
|
|
|
#ifdef REGISTER_FILTERGRAPH
|
|
// Add our graph to the running object table, which will allow
|
|
// the GraphEdit application to "spy" on our graph
|
|
hr = AddGraphToRot(g_pGraph, &g_dwGraphRegister);
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Failed to register filter graph with ROT! hr=0x%x"), hr);
|
|
g_dwGraphRegister = 0;
|
|
}
|
|
#endif
|
|
|
|
// Start previewing video data
|
|
hr = g_pMC->Run();
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Couldn't run the graph! hr=0x%x"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Remember current state
|
|
g_psCurrent = Running;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT FindCaptureDevice(IBaseFilter ** ppSrcFilter)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IBaseFilter * pSrc = NULL;
|
|
IMoniker* pMoniker =NULL;
|
|
ICreateDevEnum *pDevEnum =NULL;
|
|
IEnumMoniker *pClassEnum = NULL;
|
|
|
|
if (!ppSrcFilter)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
// Create the system device enumerator
|
|
hr = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,
|
|
IID_ICreateDevEnum, (void **) &pDevEnum);
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Couldn't create system enumerator! hr=0x%x"), hr);
|
|
}
|
|
|
|
// Create an enumerator for the video capture devices
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Couldn't create class enumerator! hr=0x%x"), hr);
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// If there are no enumerators for the requested type, then
|
|
// CreateClassEnumerator will succeed, but pClassEnum will be NULL.
|
|
if (pClassEnum == NULL)
|
|
{
|
|
MessageBox(ghApp,TEXT("No video capture device was detected.\r\n\r\n")
|
|
TEXT("This sample requires a video capture device, such as a USB WebCam,\r\n")
|
|
TEXT("to be installed and working properly. The sample will now close."),
|
|
TEXT("No Video Capture Hardware"), MB_OK | MB_ICONINFORMATION);
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
|
|
// 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 (SUCCEEDED(hr))
|
|
{
|
|
hr = pClassEnum->Next (1, &pMoniker, NULL);
|
|
if (hr == S_FALSE)
|
|
{
|
|
Msg(TEXT("Unable to access video capture device!"));
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Bind Moniker to a filter object
|
|
hr = pMoniker->BindToObject(0,0,IID_IBaseFilter, (void**)&pSrc);
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Couldn't bind moniker to filter object! hr=0x%x"), hr);
|
|
}
|
|
}
|
|
|
|
// Copy the found filter pointer to the output parameter.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*ppSrcFilter = pSrc;
|
|
(*ppSrcFilter)->AddRef();
|
|
}
|
|
|
|
SAFE_RELEASE(pSrc);
|
|
SAFE_RELEASE(pMoniker);
|
|
SAFE_RELEASE(pDevEnum);
|
|
SAFE_RELEASE(pClassEnum);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT GetInterfaces(void)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Create the filter graph
|
|
hr = CoCreateInstance (CLSID_FilterGraph, NULL, CLSCTX_INPROC,
|
|
IID_IGraphBuilder, (void **) &g_pGraph);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Create the capture graph builder
|
|
hr = CoCreateInstance (CLSID_CaptureGraphBuilder2 , NULL, CLSCTX_INPROC,
|
|
IID_ICaptureGraphBuilder2, (void **) &g_pCapture);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Obtain interfaces for media control and Video Window
|
|
hr = g_pGraph->QueryInterface(IID_IMediaControl,(LPVOID *) &g_pMC);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = g_pGraph->QueryInterface(IID_IVideoWindow, (LPVOID *) &g_pVW);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = g_pGraph->QueryInterface(IID_IMediaEventEx, (LPVOID *) &g_pME);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Set the window handle used to process graph events
|
|
hr = g_pME->SetNotifyWindow((OAHWND)ghApp, WM_GRAPHNOTIFY, 0);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
void CloseInterfaces(void)
|
|
{
|
|
// Stop previewing data
|
|
if (g_pMC)
|
|
g_pMC->StopWhenReady();
|
|
|
|
g_psCurrent = Stopped;
|
|
|
|
// Stop receiving events
|
|
if (g_pME)
|
|
g_pME->SetNotifyWindow(NULL, WM_GRAPHNOTIFY, 0);
|
|
|
|
// Relinquish ownership (IMPORTANT!) of the video window.
|
|
// Failing to call put_Owner can lead to assert failures within
|
|
// the video renderer, as it still assumes that it has a valid
|
|
// parent window.
|
|
if(g_pVW)
|
|
{
|
|
g_pVW->put_Visible(OAFALSE);
|
|
g_pVW->put_Owner(NULL);
|
|
}
|
|
|
|
#ifdef REGISTER_FILTERGRAPH
|
|
// Remove filter graph from the running object table
|
|
if (g_dwGraphRegister)
|
|
RemoveGraphFromRot(g_dwGraphRegister);
|
|
#endif
|
|
|
|
// Release DirectShow interfaces
|
|
SAFE_RELEASE(g_pMC);
|
|
SAFE_RELEASE(g_pME);
|
|
SAFE_RELEASE(g_pVW);
|
|
SAFE_RELEASE(g_pGraph);
|
|
SAFE_RELEASE(g_pCapture);
|
|
}
|
|
|
|
|
|
HRESULT SetupVideoWindow(void)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Set the video window to be a child of the main window
|
|
hr = g_pVW->put_Owner((OAHWND)ghApp);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Set video window style
|
|
hr = g_pVW->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Use helper function to position video window in client rect
|
|
// of main application window
|
|
ResizeVideoWindow();
|
|
|
|
// Make the video window visible, now that it is properly positioned
|
|
hr = g_pVW->put_Visible(OATRUE);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
void ResizeVideoWindow(void)
|
|
{
|
|
// Resize the video preview window to match owner window size
|
|
if (g_pVW)
|
|
{
|
|
RECT rc;
|
|
|
|
// Make the preview video fill our window
|
|
GetClientRect(ghApp, &rc);
|
|
g_pVW->SetWindowPosition(0, 0, rc.right, rc.bottom);
|
|
}
|
|
}
|
|
|
|
|
|
HRESULT ChangePreviewState(int nShow)
|
|
{
|
|
HRESULT hr=S_OK;
|
|
|
|
// If the media control interface isn't ready, don't call it
|
|
if (!g_pMC)
|
|
return S_OK;
|
|
|
|
if (nShow)
|
|
{
|
|
if (g_psCurrent != Running)
|
|
{
|
|
// Start previewing video data
|
|
hr = g_pMC->Run();
|
|
g_psCurrent = Running;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Stop previewing video data
|
|
hr = g_pMC->StopWhenReady();
|
|
g_psCurrent = Stopped;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
#ifdef REGISTER_FILTERGRAPH
|
|
|
|
HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
|
|
{
|
|
IMoniker * pMoniker;
|
|
IRunningObjectTable *pROT;
|
|
WCHAR wsz[128];
|
|
HRESULT hr;
|
|
|
|
if (!pUnkGraph || !pdwRegister)
|
|
return E_POINTER;
|
|
|
|
if (FAILED(GetRunningObjectTable(0, &pROT)))
|
|
return E_FAIL;
|
|
|
|
hr = StringCchPrintfW(wsz, NUMELMS(wsz), L"FilterGraph %08x pid %08x\0", (DWORD_PTR)pUnkGraph,
|
|
GetCurrentProcessId());
|
|
|
|
hr = CreateItemMoniker(L"!", wsz, &pMoniker);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Use the ROTFLAGS_REGISTRATIONKEEPSALIVE to ensure a strong reference
|
|
// to the object. Using this flag will cause the object to remain
|
|
// registered until it is explicitly revoked with the Revoke() method.
|
|
//
|
|
// Not using this flag means that if GraphEdit remotely connects
|
|
// to this graph and then GraphEdit exits, this object registration
|
|
// will be deleted, causing future attempts by GraphEdit to fail until
|
|
// this application is restarted or until the graph is registered again.
|
|
hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,
|
|
pMoniker, pdwRegister);
|
|
pMoniker->Release();
|
|
}
|
|
|
|
pROT->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
// Removes a filter graph from the Running Object Table
|
|
void RemoveGraphFromRot(DWORD pdwRegister)
|
|
{
|
|
IRunningObjectTable *pROT;
|
|
|
|
if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
|
|
{
|
|
pROT->Revoke(pdwRegister);
|
|
pROT->Release();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void Msg(TCHAR *szFormat, ...)
|
|
{
|
|
TCHAR szBuffer[1024]; // Large buffer for long filenames or URLs
|
|
const size_t NUMCHARS = sizeof(szBuffer) / sizeof(szBuffer[0]);
|
|
const int LASTCHAR = NUMCHARS - 1;
|
|
|
|
// Format the input string
|
|
va_list pArgs;
|
|
va_start(pArgs, szFormat);
|
|
|
|
// Use a bounded buffer size to prevent buffer overruns. Limit count to
|
|
// character size minus one to allow for a NULL terminating character.
|
|
(void)StringCchVPrintf(szBuffer, NUMCHARS - 1, szFormat, pArgs);
|
|
va_end(pArgs);
|
|
|
|
// Ensure that the formatted string is NULL-terminated
|
|
szBuffer[LASTCHAR] = TEXT('\0');
|
|
|
|
MessageBox(NULL, szBuffer, TEXT("PlayCap Message"), MB_OK | MB_ICONERROR);
|
|
}
|
|
|
|
|
|
HRESULT HandleGraphEvent(void)
|
|
{
|
|
LONG evCode;
|
|
LONG_PTR evParam1, evParam2;
|
|
HRESULT hr=S_OK;
|
|
|
|
if (!g_pME)
|
|
return E_POINTER;
|
|
|
|
while(SUCCEEDED(g_pME->GetEvent(&evCode, &evParam1, &evParam2, 0)))
|
|
{
|
|
//
|
|
// Free event parameters to prevent memory leaks associated with
|
|
// event parameter data. While this application is not interested
|
|
// in the received events, applications should always process them.
|
|
//
|
|
hr = g_pME->FreeEventParams(evCode, evParam1, evParam2);
|
|
|
|
// Insert event processing code here, if desired
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
LRESULT CALLBACK WndMainProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_GRAPHNOTIFY:
|
|
HandleGraphEvent();
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
ResizeVideoWindow();
|
|
break;
|
|
|
|
case WM_WINDOWPOSCHANGED:
|
|
ChangePreviewState(! (IsIconic(hwnd)));
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
// Hide the main window while the graph is destroyed
|
|
ShowWindow(ghApp, SW_HIDE);
|
|
CloseInterfaces(); // Stop capturing and release interfaces
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
|
|
// Pass this message to the video window for notification of system changes
|
|
if (g_pVW)
|
|
g_pVW->NotifyOwnerMessage((LONG_PTR) hwnd, message, wParam, lParam);
|
|
|
|
return DefWindowProc (hwnd , message, wParam, lParam);
|
|
}
|
|
|
|
|
|
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)
|
|
{
|
|
MSG msg={0};
|
|
WNDCLASS wc;
|
|
|
|
// Initialize COM
|
|
if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)))
|
|
{
|
|
Msg(TEXT("CoInitialize Failed!\r\n"));
|
|
exit(1);
|
|
}
|
|
|
|
// Register the window class
|
|
ZeroMemory(&wc, sizeof wc);
|
|
wc.lpfnWndProc = WndMainProc;
|
|
wc.hInstance = hInstance;
|
|
wc.lpszClassName = CLASSNAME;
|
|
wc.lpszMenuName = NULL;
|
|
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hIcon = NULL;
|
|
if(!RegisterClass(&wc))
|
|
{
|
|
Msg(TEXT("RegisterClass Failed! Error=0x%x\r\n"), GetLastError());
|
|
CoUninitialize();
|
|
exit(1);
|
|
}
|
|
|
|
// Create the main window. The WS_CLIPCHILDREN style is required.
|
|
ghApp = CreateWindow(CLASSNAME, APPLICATIONNAME,
|
|
WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_CLIPCHILDREN,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT,
|
|
0, 0, hInstance, 0);
|
|
|
|
if(ghApp)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Create DirectShow graph and start capturing video
|
|
hr = CaptureVideo();
|
|
if (FAILED (hr))
|
|
{
|
|
CloseInterfaces();
|
|
DestroyWindow(ghApp);
|
|
}
|
|
else
|
|
{
|
|
// Don't display the main window until the DirectShow
|
|
// preview graph has been created. Once video data is
|
|
// being received and processed, the window will appear
|
|
// and immediately have useful video data to display.
|
|
// Otherwise, it will be black until video data arrives.
|
|
ShowWindow(ghApp, nCmdShow);
|
|
}
|
|
|
|
// Main message loop
|
|
while(GetMessage(&msg,NULL,0,0))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
// Release COM
|
|
CoUninitialize();
|
|
|
|
return (int) msg.wParam;
|
|
}
|
|
|
|
|
|
|