You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2339 lines
70 KiB
2339 lines
70 KiB
/*****************************************************************************
|
|
*
|
|
* (C) COPYRIGHT MICROSOFT CORPORATION, 1999-2000
|
|
*
|
|
* TITLE: PrvGrph.cpp
|
|
*
|
|
* VERSION: 1.0
|
|
*
|
|
* AUTHOR: OrenR
|
|
*
|
|
* DATE: 2000/10/25
|
|
*
|
|
* DESCRIPTION: Implements preview graph for capture still images
|
|
*
|
|
*****************************************************************************/
|
|
#include <precomp.h>
|
|
#pragma hdrstop
|
|
|
|
///////////////////////////////
|
|
//
|
|
// Constants
|
|
//
|
|
///////////////////////////////
|
|
|
|
//
|
|
// Amount of time we are willing to wait to take a picture
|
|
//
|
|
const UINT TIMEOUT_TAKE_PICTURE = 1000 * 5; // 5 seconds
|
|
|
|
//
|
|
// These 2 values define how many media sample packets the still
|
|
// filter should cache
|
|
//
|
|
const UINT CAPTURE_NUM_SAMPLES_TO_CACHE = 6;
|
|
const UINT STILL_NUM_SAMPLES_TO_CACHE = 1;
|
|
|
|
//
|
|
// Max amount of time we wait for us to transition into a running state
|
|
//
|
|
const UINT STATE_TRANSITION_TIMEOUT = 1000 * 2; // 2 seconds
|
|
|
|
|
|
//
|
|
// Video size preferrences. This will not affect DV devices, only
|
|
// USB WebCams that support changing their format.
|
|
//
|
|
const GUID DEFAULT_MEDIASUBTYPE = MEDIASUBTYPE_IYUV;
|
|
const LONG MIN_VIDEO_WIDTH = 176;
|
|
const LONG MIN_VIDEO_HEIGHT = 144;
|
|
const LONG MAX_VIDEO_WIDTH = 640;
|
|
const LONG MAX_VIDEO_HEIGHT = 480;
|
|
|
|
const LONG PREFERRED_VIDEO_WIDTH = 176;
|
|
const LONG PREFERRED_VIDEO_HEIGHT = 144;
|
|
|
|
const LONG PREFERRED_FRAME_RATE = 30; // ideal frame rate
|
|
const LONG BACKUP_FRAME_RATE = 15; // less than ideal frame rate.
|
|
|
|
|
|
///////////////////////////////
|
|
// CPreviewGraph Constructor
|
|
//
|
|
CPreviewGraph::CPreviewGraph() :
|
|
m_hwndParent(NULL),
|
|
m_lStillPinCaps(0),
|
|
m_lStyle(0),
|
|
m_bPreviewVisible(TRUE),
|
|
m_CurrentState(WIAVIDEO_NO_VIDEO),
|
|
m_pWiaVideo(NULL),
|
|
m_bSizeVideoToWindow(FALSE),
|
|
m_pVideoProperties(NULL)
|
|
{
|
|
DBG_FN("CPreviewGraph::CPreviewGraph");
|
|
}
|
|
|
|
///////////////////////////////
|
|
// CPreviewGraph Destructor
|
|
//
|
|
CPreviewGraph::~CPreviewGraph()
|
|
{
|
|
DBG_FN("CPreviewGraph::~CPreviewGraph");
|
|
|
|
//
|
|
// Term the object if it is not already terminated.
|
|
//
|
|
|
|
Term();
|
|
}
|
|
|
|
///////////////////////////////
|
|
// Init
|
|
//
|
|
HRESULT CPreviewGraph::Init(CWiaVideo *pWiaVideo)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (pWiaVideo == NULL)
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Init, received NULL CWiaVideo "
|
|
"param, this should never happen"));
|
|
}
|
|
|
|
if (pWiaVideo)
|
|
{
|
|
m_pWiaVideo = pWiaVideo;
|
|
}
|
|
|
|
m_StillProcessor.Init(this);
|
|
|
|
CreateHiddenWindow();
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// Term
|
|
//
|
|
HRESULT CPreviewGraph::Term()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
m_StillProcessor.Term();
|
|
|
|
if (GetState() != WIAVIDEO_NO_VIDEO)
|
|
{
|
|
DestroyVideo();
|
|
}
|
|
|
|
DestroyHiddenWindow();
|
|
|
|
m_pWiaVideo = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// GetImagesDirectory
|
|
//
|
|
HRESULT CPreviewGraph::GetImagesDirectory(CSimpleString *pImagesDirectory)
|
|
{
|
|
DBG_FN("CPreviewGraph::GetImagesDirectory");
|
|
|
|
ASSERT(pImagesDirectory != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (pImagesDirectory == NULL)
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::GetImagesDirectory received a "
|
|
"NULL parameter"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
*pImagesDirectory = m_strImagesDirectory;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// SetImagesDirectory
|
|
//
|
|
HRESULT CPreviewGraph::SetImagesDirectory(
|
|
const CSimpleString *pImagesDirectory)
|
|
{
|
|
DBG_FN("CPreviewGraph::SetImagesDirectory");
|
|
|
|
ASSERT(pImagesDirectory != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (pImagesDirectory == NULL)
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::SetImagesDirectory received a "
|
|
"NULL parameter"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
m_strImagesDirectory = *pImagesDirectory;
|
|
|
|
//
|
|
// If the graph is created, then we should set the image directory
|
|
// so that the next image captured will be saved to the new directory.
|
|
//
|
|
if (GetState() == WIAVIDEO_NO_VIDEO)
|
|
{
|
|
hr = m_StillProcessor.CreateImageDir(&m_strImagesDirectory);
|
|
CHECK_S_OK2(hr,
|
|
("CPreviewGraph::SetImagesDirectory, failed to "
|
|
"create images directory '%ls'",
|
|
CSimpleStringConvert::WideString(m_strImagesDirectory)));
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// GetState
|
|
//
|
|
WIAVIDEO_STATE CPreviewGraph::GetState()
|
|
{
|
|
return m_CurrentState;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// SetState
|
|
//
|
|
HRESULT CPreviewGraph::SetState(WIAVIDEO_STATE NewState)
|
|
{
|
|
m_CurrentState = NewState;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// CreateVideo
|
|
//
|
|
HRESULT CPreviewGraph::CreateVideo(const TCHAR *pszOptionalWiaDeviceID,
|
|
IMoniker *pCaptureDeviceMoniker,
|
|
HWND hwndParent,
|
|
BOOL bStretchToFitParent,
|
|
BOOL bAutoPlay)
|
|
{
|
|
DBG_FN("CPreviewGraph::CreateVideo");
|
|
|
|
HRESULT hr = S_OK;
|
|
WIAVIDEO_STATE State = GetState();
|
|
|
|
if ((m_strImagesDirectory.Length() == 0) ||
|
|
(pCaptureDeviceMoniker == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::CreateVideo, received NULL param"));
|
|
return hr;
|
|
}
|
|
else if ((State == WIAVIDEO_DESTROYING_VIDEO) ||
|
|
(State == WIAVIDEO_CREATING_VIDEO))
|
|
{
|
|
hr = E_FAIL;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::CreateVideo, cannot create video, "
|
|
"still in the process of creating or destroying "
|
|
"it, CurrentState = '%lu'", State));
|
|
|
|
return hr;
|
|
}
|
|
else if (State != WIAVIDEO_NO_VIDEO)
|
|
{
|
|
//
|
|
// If we are not in the process of creating or destorying the
|
|
// video, and our state is not NO_VIDEO, then assume everything
|
|
// is okay, and return S_OK.
|
|
//
|
|
return S_OK;
|
|
}
|
|
|
|
ASSERT(m_strImagesDirectory.Length() != 0);
|
|
ASSERT(pCaptureDeviceMoniker != NULL);
|
|
|
|
//
|
|
// Set our state to indicate we are creating the video
|
|
//
|
|
SetState(WIAVIDEO_CREATING_VIDEO);
|
|
|
|
//
|
|
// Create our image directory
|
|
//
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Save the parent window handle.
|
|
//
|
|
m_hwndParent = hwndParent;
|
|
m_bSizeVideoToWindow = bStretchToFitParent;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pVideoProperties = new CWiaVideoProperties(pszOptionalWiaDeviceID);
|
|
|
|
if (m_pVideoProperties == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::CreateVideo, failed to allocate memory "
|
|
"for CWiaVideoProperties. Cannot create video"));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Build the DirectShow video preview graph
|
|
//
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = BuildPreviewGraph(pCaptureDeviceMoniker, bStretchToFitParent);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::CreateVideo failed to build the "
|
|
"preview graph"));
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Video graph exists, update our state
|
|
//
|
|
SetState(WIAVIDEO_VIDEO_CREATED);
|
|
|
|
//
|
|
// Begin playback automatically
|
|
//
|
|
if (bAutoPlay)
|
|
{
|
|
hr = Play();
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::CreateVideo failed begin "
|
|
"playback"));
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
CHECK_S_OK2(hr, ("CreateVideo failed to build the graph, tearing it "
|
|
"down"));
|
|
|
|
DestroyVideo();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// DestroyVideo
|
|
//
|
|
HRESULT CPreviewGraph::DestroyVideo()
|
|
{
|
|
DBG_FN("CPreviewGraph::DestroyVideo");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
SetState(WIAVIDEO_DESTROYING_VIDEO);
|
|
|
|
//
|
|
// Stop the graph first
|
|
//
|
|
Stop();
|
|
|
|
//
|
|
// Delete the video properties object.
|
|
//
|
|
if (m_pVideoProperties)
|
|
{
|
|
delete m_pVideoProperties;
|
|
m_pVideoProperties = NULL;
|
|
}
|
|
|
|
//
|
|
// Destroys the preview graph and all DShow components associated with it.
|
|
// Even if these are already gone, there is no harm in calling it.
|
|
//
|
|
TeardownPreviewGraph();
|
|
|
|
m_hwndParent = NULL;
|
|
|
|
m_pVideoControl = NULL;
|
|
m_pStillPin = NULL;
|
|
m_pCapturePinSnapshot = NULL;
|
|
m_pStillPinSnapshot = NULL;
|
|
m_pPreviewVW = NULL;
|
|
m_pCaptureGraphBuilder = NULL;
|
|
m_pGraphBuilder = NULL;
|
|
m_pCaptureFilter = NULL;
|
|
m_lStillPinCaps = 0;
|
|
m_lStyle = 0;
|
|
|
|
SetState(WIAVIDEO_NO_VIDEO);
|
|
|
|
//
|
|
// We purposely put this here so that it does NOT get reset after
|
|
// destroying the video. This should remain the lifetime of this
|
|
// object instance, unless changed by the user via the
|
|
// get/put_PreviewVisible properties in CWiaVideo.
|
|
//
|
|
m_bPreviewVisible = m_bPreviewVisible;
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// TakePicture
|
|
//
|
|
HRESULT CPreviewGraph::TakePicture(CSimpleString *pstrNewImageFileName)
|
|
{
|
|
DBG_FN("CPreviewGraph::TakePicture");
|
|
|
|
WIAVIDEO_STATE State = GetState();
|
|
|
|
HRESULT hr = S_OK;
|
|
CSimpleString strNewImageFullPath;
|
|
|
|
if ((State != WIAVIDEO_VIDEO_PLAYING) &&
|
|
(State != WIAVIDEO_VIDEO_PAUSED))
|
|
{
|
|
hr = E_FAIL;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::TakePicture, cannot take a picture "
|
|
"because we are in an incorrect state, "
|
|
"current state = '%lu'", State));
|
|
|
|
return hr;
|
|
}
|
|
|
|
ASSERT((State == WIAVIDEO_VIDEO_PLAYING) ||
|
|
(State == WIAVIDEO_VIDEO_PAUSED));
|
|
|
|
//
|
|
// Set this to TRUE. We reset it when we return from our
|
|
// WaitForNewImage fn. This allows us to distinguish between
|
|
// a user initiated take picture event, and an async take picture
|
|
// event generated by a hardware button push.
|
|
//
|
|
m_StillProcessor.SetTakePicturePending(TRUE);
|
|
|
|
//
|
|
// If the device is internal triggerable, trigger it
|
|
// The triggered image will be delivered to the still pin and will
|
|
// then travel down stream until it reaches the WIA StreamSnapshot filter,
|
|
// which will process the image.
|
|
//
|
|
|
|
if (m_pVideoControl && (m_lStillPinCaps & VideoControlFlag_Trigger))
|
|
{
|
|
//
|
|
// ignore the time stamp here since we do not need it.
|
|
//
|
|
|
|
hr = m_pVideoControl->SetMode(m_pStillPin, VideoControlFlag_Trigger);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::TakePicture, attempted to trigger "
|
|
"the still pin on the capture filter to take a "
|
|
"picture, but it failed"));
|
|
|
|
}
|
|
else
|
|
{
|
|
if (m_pCapturePinSnapshot)
|
|
{
|
|
hr = m_pCapturePinSnapshot->Snapshot(GetMessageTime());
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::TakePicture, attempted to "
|
|
"trigger the WIA Image filter to take a "
|
|
"snapshot of the video stream, but it "
|
|
"failed"));
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::TakePicture, attempted to call "
|
|
"snapshot on the WIA Image filter, but the "
|
|
"filter pointer is NULL"));
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we are taking the picture via the still pin, then taking a
|
|
// picture is an asynchronous operation, and we have to wait for
|
|
// the StillProcessor's callback function to complete its work.
|
|
// It will signal us when it's done. If we are taking the picture
|
|
// via the WIA image filter, then the operation is synchronous,
|
|
// in which case this wait function will return immediately.
|
|
//
|
|
hr = m_StillProcessor.WaitForNewImage(TIMEOUT_TAKE_PICTURE,
|
|
&strNewImageFullPath);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::TakePicture failed waiting for new "
|
|
"still image to arrive, our timeout was '%d'",
|
|
TIMEOUT_TAKE_PICTURE));
|
|
|
|
//
|
|
// Set this to TRUE. We reset it when we return from our
|
|
// WaitForNewImage fn.
|
|
//
|
|
m_StillProcessor.SetTakePicturePending(FALSE);
|
|
|
|
if ((pstrNewImageFileName) && (strNewImageFullPath.Length() > 0))
|
|
{
|
|
*pstrNewImageFileName = strNewImageFullPath;
|
|
}
|
|
|
|
CHECK_S_OK(hr);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// ResizeVideo
|
|
//
|
|
HRESULT CPreviewGraph::ResizeVideo(BOOL bSizeVideoToWindow)
|
|
{
|
|
DBG_FN("CPreviewGraph::ResizeVideo");
|
|
|
|
RECT rc = {0};
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Check for invalid args
|
|
//
|
|
|
|
if ((m_hwndParent) && (m_pPreviewVW))
|
|
{
|
|
hr = CDShowUtil::SizeVideoToWindow(m_hwndParent,
|
|
m_pPreviewVW,
|
|
bSizeVideoToWindow);
|
|
|
|
m_bSizeVideoToWindow = bSizeVideoToWindow;
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ResizeVideo, failed to resize "
|
|
"video window"));
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ResizeVideo, either the parent "
|
|
"window is NULL (0x%08lx), or the Video Preview "
|
|
"pointer is NULL (0x%08lx)",
|
|
m_hwndParent, m_pPreviewVW));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////
|
|
// ShowVideo
|
|
//
|
|
HRESULT CPreviewGraph::ShowVideo(BOOL bShow)
|
|
{
|
|
DBG_FN("CPreviewGraph::ShowVideo");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pPreviewVW)
|
|
{
|
|
hr = CDShowUtil::ShowVideo(bShow, m_pPreviewVW);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ShowVideo failed"));
|
|
}
|
|
|
|
m_bPreviewVisible = bShow;
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// Play
|
|
//
|
|
HRESULT CPreviewGraph::Play()
|
|
{
|
|
DBG_FN("CPreviewGraph::Play");
|
|
|
|
HRESULT hr = S_OK;
|
|
WIAVIDEO_STATE State = GetState();
|
|
|
|
if ((State != WIAVIDEO_VIDEO_CREATED) &&
|
|
(State != WIAVIDEO_VIDEO_PAUSED))
|
|
{
|
|
hr = E_FAIL;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Play, cannot begin playback "
|
|
"because we are in an incorrect state, "
|
|
"current state = '%lu'", State));
|
|
|
|
return hr;
|
|
}
|
|
else if (State == WIAVIDEO_VIDEO_PLAYING)
|
|
{
|
|
DBG_WRN(("CPreviewGraph::Play, play was called, but we are already "
|
|
"playing, doing nothing."));
|
|
|
|
return hr;
|
|
}
|
|
|
|
ASSERT(m_pGraphBuilder != NULL);
|
|
|
|
if (m_pGraphBuilder == NULL)
|
|
{
|
|
hr = E_FAIL;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Play, m_pGraphBuilder is NULL"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
CComQIPtr<IMediaControl, &IID_IMediaControl> pMC(m_pGraphBuilder);
|
|
|
|
if (pMC)
|
|
{
|
|
DBG_TRC(("CPreviewGraph::Play ***Beginning Playback ***"));
|
|
|
|
//
|
|
// Set the graph running...
|
|
//
|
|
hr = pMC->Run();
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
DBG_TRC(("CPreviewGraph::Play, graph is running..."));
|
|
|
|
SetState(WIAVIDEO_VIDEO_PLAYING);
|
|
}
|
|
else if (hr == S_FALSE)
|
|
{
|
|
OAFilterState FilterState;
|
|
|
|
DBG_TRC(("CPreviewGraph::Play, Waiting '%lu' millisec for "
|
|
"graph to transition to running state",
|
|
STATE_TRANSITION_TIMEOUT));
|
|
|
|
//
|
|
// Give the graph a chance to transition into the running
|
|
// state. Note that this function will wait for
|
|
// STATE_TRANSITION_TIMEOUT milliseconds, so make sure that
|
|
// this is not a long wait, otherwise the caller could
|
|
// appear unresponsive.
|
|
//
|
|
hr = pMC->GetState(STATE_TRANSITION_TIMEOUT, &FilterState);
|
|
|
|
if ((hr == S_OK) && (FilterState == State_Running))
|
|
{
|
|
SetState(WIAVIDEO_VIDEO_PLAYING);
|
|
|
|
DBG_TRC(("CPreviewGraph::Play, graph is running..."));
|
|
}
|
|
else if (hr == VFW_S_STATE_INTERMEDIATE)
|
|
{
|
|
//
|
|
// We fudge our state a little here on the assumption
|
|
// that the transition to the run state by DShow is
|
|
// taking a little longer, but eventually will transition
|
|
// to running.
|
|
//
|
|
SetState(WIAVIDEO_VIDEO_PLAYING);
|
|
|
|
DBG_TRC(("CPreviewGraph::Play, still transitioning to "
|
|
"play state..."));
|
|
}
|
|
else
|
|
{
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Play, "
|
|
"IMediaControl::GetState failed..."));
|
|
}
|
|
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Play, "
|
|
"IMediaControl::Run failed"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBG_ERR(("CPreviewGraph::Play, Unable to get "
|
|
"MediaControl interface"));
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the user of this object specified in the past that the video window
|
|
// should be visible, then show it, otherwise, make sure it is hidden.
|
|
//
|
|
|
|
ResizeVideo(m_bSizeVideoToWindow);
|
|
ShowVideo(m_bPreviewVisible);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////
|
|
// Stop
|
|
//
|
|
HRESULT CPreviewGraph::Stop()
|
|
{
|
|
DBG_FN("CPreviewGraph::Stop");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pGraphBuilder)
|
|
{
|
|
CComQIPtr<IMediaControl, &IID_IMediaControl> pMC(m_pGraphBuilder);
|
|
|
|
if (pMC)
|
|
{
|
|
hr = pMC->Stop();
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Stop, IMediaControl::Stop "
|
|
"failed"));
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
DBG_ERR(("CPreviewGraph::Stop unable to get MediaControl "
|
|
"interface, returning hr = 0x%08lx", hr));
|
|
}
|
|
}
|
|
|
|
CHECK_S_OK(hr);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// Pause
|
|
//
|
|
HRESULT CPreviewGraph::Pause()
|
|
{
|
|
DBG_FN("CPreviewGraph::Pause");
|
|
|
|
HRESULT hr = S_OK;
|
|
WIAVIDEO_STATE State = GetState();
|
|
|
|
if ((State != WIAVIDEO_VIDEO_CREATED) &&
|
|
(State != WIAVIDEO_VIDEO_PLAYING))
|
|
{
|
|
hr = E_FAIL;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Pause, cannot begin pause "
|
|
"because we are in an incorrect state, "
|
|
"current state = '%lu'", State));
|
|
|
|
return hr;
|
|
}
|
|
else if (State == WIAVIDEO_VIDEO_PAUSED)
|
|
{
|
|
DBG_WRN(("CPreviewGraph::Pause, pause was called, but we are already "
|
|
"paused, doing nothing."));
|
|
|
|
return hr;
|
|
}
|
|
|
|
CComQIPtr<IMediaControl, &IID_IMediaControl> pMC(m_pGraphBuilder);
|
|
|
|
if (pMC)
|
|
{
|
|
hr = pMC->Pause();
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
SetState(WIAVIDEO_VIDEO_PAUSED);
|
|
}
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::Pause, failed to pause video"));
|
|
}
|
|
else
|
|
{
|
|
DBG_ERR(("CPreviewGraph::Pause unable to get MediaControl interface"));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// ProcessAsyncImage
|
|
//
|
|
// Called by CPreviewGraph
|
|
// when user presses hardware
|
|
// button and it is delivered to
|
|
// Still Pin.
|
|
//
|
|
HRESULT CPreviewGraph::ProcessAsyncImage(const CSimpleString *pNewImage)
|
|
{
|
|
DBG_FN("CPreviewGraph::ProcessAsyncImage");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_pWiaVideo)
|
|
{
|
|
hr = m_pWiaVideo->ProcessAsyncImage(pNewImage);
|
|
}
|
|
else
|
|
{
|
|
DBG_WRN(("CPreviewGraph::ProcessAsyncImage failed, m_pWiaVideo "
|
|
"is NULL"));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////
|
|
// GetStillPinCaps
|
|
//
|
|
HRESULT CPreviewGraph::GetStillPinCaps(IBaseFilter *pCaptureFilter,
|
|
IPin **ppStillPin,
|
|
IAMVideoControl **ppVideoControl,
|
|
LONG *plCaps)
|
|
{
|
|
DBG_FN("CPreviewGraph::GetStillPinCaps");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(pCaptureFilter != NULL);
|
|
ASSERT(ppStillPin != NULL);
|
|
ASSERT(ppVideoControl != NULL);
|
|
ASSERT(plCaps != NULL);
|
|
|
|
if ((pCaptureFilter == NULL) ||
|
|
(ppStillPin == NULL) ||
|
|
(ppVideoControl == NULL) ||
|
|
(plCaps == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::GetStillPinCaps received a NULL "
|
|
"param"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Attempt to find the still pin on the capture filter
|
|
// This will decide the type of graph we are going to build
|
|
//
|
|
hr = m_pCaptureGraphBuilder->FindInterface(&PIN_CATEGORY_STILL,
|
|
&MEDIATYPE_Video,
|
|
pCaptureFilter,
|
|
IID_IPin,
|
|
(void **)ppStillPin);
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
// determine if it is triggerable or not.
|
|
hr = m_pCaptureGraphBuilder->FindInterface(&PIN_CATEGORY_STILL,
|
|
&MEDIATYPE_Video,
|
|
pCaptureFilter,
|
|
IID_IAMVideoControl,
|
|
(void **)ppVideoControl);
|
|
|
|
if ((hr == S_OK) && (*ppVideoControl) && (*ppStillPin))
|
|
{
|
|
hr = (*ppVideoControl)->GetCaps(*ppStillPin, plCaps);
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// If the still pin cannot be triggered externally or internally
|
|
// then it is useless to us, so just ignore it.
|
|
//
|
|
if (!(*plCaps &
|
|
(VideoControlFlag_ExternalTriggerEnable |
|
|
VideoControlFlag_Trigger)))
|
|
{
|
|
*plCaps = 0;
|
|
*ppStillPin = NULL;
|
|
*ppVideoControl = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBG_PRT(("CPreviewGraph::GetStillPinCaps, Capture Filter does not "
|
|
"have a still pin"));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// AddStillFilterToGraph
|
|
//
|
|
// Add the WIA Stream Snapshot
|
|
// filter to our graph
|
|
//
|
|
HRESULT CPreviewGraph::AddStillFilterToGraph(LPCWSTR pwszFilterName,
|
|
IBaseFilter **ppFilter,
|
|
IStillSnapshot **ppSnapshot)
|
|
{
|
|
DBG_FN("CPreviewGraph::AddStillFilterToGraph");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(pwszFilterName != NULL);
|
|
ASSERT(ppFilter != NULL);
|
|
ASSERT(ppSnapshot != NULL);
|
|
|
|
if ((pwszFilterName == NULL) ||
|
|
(ppFilter == NULL) ||
|
|
(ppSnapshot == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddStillFilterToGraph received "
|
|
"NULL params"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Create the still filter
|
|
//
|
|
hr = CoCreateInstance(CLSID_STILL_FILTER,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IBaseFilter,
|
|
(void**)ppFilter);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddStillFilterToGraph failed to "
|
|
"CoCreate Still Image Filter"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Add the still filter to the graph.
|
|
//
|
|
hr = m_pGraphBuilder->AddFilter(*ppFilter, pwszFilterName);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddStillFilterToGraph failed to "
|
|
"add '%ls' filter to the graph", pwszFilterName));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = (*ppFilter)->QueryInterface(IID_IStillSnapshot,
|
|
(void **)ppSnapshot);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddStillFilterToGraph, failed "
|
|
"to get IStillSnapshot interface on still filter"));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// AddColorConverterToGraph
|
|
//
|
|
// Creates the capture filter
|
|
// identified by the device ID
|
|
// and returns it.
|
|
//
|
|
HRESULT CPreviewGraph::AddColorConverterToGraph(LPCWSTR pwszFilterName,
|
|
IBaseFilter **ppColorSpaceConverter)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(pwszFilterName != NULL);
|
|
ASSERT(ppColorSpaceConverter != NULL);
|
|
|
|
if ((pwszFilterName == NULL) ||
|
|
(ppColorSpaceConverter == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddColorConverterToGraph, "
|
|
"received a NULL pointer"));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Create the Color Converter filter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = CoCreateInstance(CLSID_Colour,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IBaseFilter,
|
|
(void**) ppColorSpaceConverter);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddColorConverterToGraph failed to "
|
|
"create the DShow Color Converter Filter"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = m_pGraphBuilder->AddFilter(*ppColorSpaceConverter, pwszFilterName);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddColorConverterToGraph failed to "
|
|
"add '%ls' filter to the graph", pwszFilterName));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// AddVideoRendererToGraph
|
|
//
|
|
//
|
|
HRESULT CPreviewGraph::AddVideoRendererToGraph(LPCWSTR pwszFilterName,
|
|
IBaseFilter **ppVideoRenderer)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(pwszFilterName != NULL);
|
|
ASSERT(ppVideoRenderer != NULL);
|
|
|
|
if ((pwszFilterName == NULL) ||
|
|
(ppVideoRenderer == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddVideoRendererToGraph, "
|
|
"received a NULL pointer"));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Create the Video Renderer filter.
|
|
//
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
BOOL bUseVMR = FALSE;
|
|
|
|
//
|
|
// even if we fail, in the worst case we don't use the vmr filter.
|
|
// Not the end of the world.
|
|
//
|
|
CWiaUtil::GetUseVMR(&bUseVMR);
|
|
|
|
if (bUseVMR)
|
|
{
|
|
hr = CoCreateInstance(CLSID_VideoMixingRenderer,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IBaseFilter,
|
|
(void**) ppVideoRenderer);
|
|
}
|
|
else
|
|
{
|
|
hr = CoCreateInstance(CLSID_VideoRenderer,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IBaseFilter,
|
|
(void**) ppVideoRenderer);
|
|
}
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddVideoRendererToGraph failed to "
|
|
"create the DShow Video Renderer Filter"));
|
|
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = m_pGraphBuilder->AddFilter(*ppVideoRenderer, pwszFilterName);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddVideoRenderer failed to "
|
|
"add '%ls' filter to the graph", pwszFilterName));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////
|
|
// InitVideoWindows
|
|
//
|
|
// Initialize the video windows
|
|
// so that they don't have
|
|
// an owner, and they are not
|
|
// visible.
|
|
//
|
|
|
|
HRESULT CPreviewGraph::InitVideoWindows(HWND hwndParent,
|
|
IBaseFilter *pCaptureFilter,
|
|
IVideoWindow **ppPreviewVideoWindow,
|
|
BOOL bStretchToFitParent)
|
|
{
|
|
DBG_FN("CPreviewGraph::InitVideoWindows");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(pCaptureFilter != NULL);
|
|
ASSERT(ppPreviewVideoWindow != NULL);
|
|
|
|
if ((pCaptureFilter == NULL) ||
|
|
(ppPreviewVideoWindow == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::InitVideoWindows received NULL "
|
|
"params"));
|
|
}
|
|
|
|
|
|
//
|
|
// If the still pin exists, make sure that the video renderer hanging
|
|
// off of this path in the graph is NOT visible.
|
|
//
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
if (m_pStillPin)
|
|
{
|
|
CComPtr<IVideoWindow> pStillVW;
|
|
|
|
hr = m_pCaptureGraphBuilder->FindInterface(&PIN_CATEGORY_STILL,
|
|
&MEDIATYPE_Video,
|
|
pCaptureFilter,
|
|
IID_IVideoWindow,
|
|
(void**)&pStillVW);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::InitVideoWindows failed to "
|
|
"find video renderer off of the still "
|
|
"filter pin"));
|
|
|
|
//
|
|
// We hide the video renderer attached to the still pin stream
|
|
// since it will contain the still image, which we save to a
|
|
// file, rather than show it on the desktop.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
CDShowUtil::ShowVideo(FALSE, pStillVW);
|
|
CDShowUtil::SetVideoWindowParent(NULL, pStillVW, NULL);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this fails, it is not fatal
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Find the video renderer hanging off of the capture pin path in
|
|
// the graph and make sure that it is made the child of the
|
|
// parent window.
|
|
|
|
hr = m_pCaptureGraphBuilder->FindInterface(
|
|
&PIN_CATEGORY_CAPTURE,
|
|
&MEDIATYPE_Video,
|
|
pCaptureFilter,
|
|
IID_IVideoWindow,
|
|
(void **)ppPreviewVideoWindow);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::InitVideoWindows, failed to "
|
|
"find video renderer off of capture/preview pin"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Hide the video window before we set it's parent.
|
|
//
|
|
CDShowUtil::ShowVideo(FALSE, *ppPreviewVideoWindow);
|
|
|
|
//
|
|
// Set the video window's parent.
|
|
//
|
|
CDShowUtil::SetVideoWindowParent(hwndParent,
|
|
*ppPreviewVideoWindow,
|
|
&m_lStyle);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// CreateCaptureFilter
|
|
//
|
|
// Creates the capture filter
|
|
// identified by the device ID
|
|
// and returns it.
|
|
//
|
|
HRESULT CPreviewGraph::CreateCaptureFilter(IMoniker *pCaptureDeviceMoniker,
|
|
IBaseFilter **ppCaptureFilter)
|
|
{
|
|
DBG_FN("CPreviewGraph::CreateCaptureFilter");
|
|
|
|
ASSERT(pCaptureDeviceMoniker != NULL);
|
|
ASSERT(ppCaptureFilter != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if ((pCaptureDeviceMoniker == NULL) ||
|
|
(ppCaptureFilter == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::CreateCaptureFilter received NULL "
|
|
"params"));
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
CDShowUtil::DumpCaptureMoniker(pCaptureDeviceMoniker);
|
|
#endif
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = pCaptureDeviceMoniker->BindToObject(0,
|
|
0,
|
|
IID_IBaseFilter,
|
|
(void**)ppCaptureFilter);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::CreateCaptureFilter failed to bind "
|
|
"to device's moniker."));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// AddCaptureFilterToGraph
|
|
//
|
|
HRESULT CPreviewGraph::AddCaptureFilterToGraph(IBaseFilter *pCaptureFilter,
|
|
IPin **ppCapturePin)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CComPtr<IPin> pCapturePin;
|
|
GUID *pMediaSubType = NULL;
|
|
LONG lWidth = 0;
|
|
LONG lHeight = 0;
|
|
LONG lFrameRate = 0;
|
|
|
|
ASSERT(pCaptureFilter != NULL);
|
|
ASSERT(ppCapturePin != NULL);
|
|
|
|
if ((pCaptureFilter == NULL) ||
|
|
(ppCapturePin == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddCaptureFilterToGraph, received "
|
|
"a NULL pointer"));
|
|
|
|
return hr;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pGraphBuilder->AddFilter(pCaptureFilter,
|
|
L"Capture Filter");
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddCaptureFilterToGraph, failed to "
|
|
"add capture filter to graph"));
|
|
}
|
|
|
|
//
|
|
// Find the capture pin so we can render it.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = m_pCaptureGraphBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
|
|
&MEDIATYPE_Video,
|
|
pCaptureFilter,
|
|
IID_IPin,
|
|
(void **)&pCapturePin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::AddCaptureFilterToGraph failed to "
|
|
"find Capture pin on capture filter"));
|
|
}
|
|
|
|
//
|
|
// Get all the video properties we can about this filter.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CDShowUtil::GetVideoProperties(pCaptureFilter,
|
|
pCapturePin,
|
|
m_pVideoProperties);
|
|
}
|
|
|
|
//
|
|
// We set the video width and height to the preferred setting size if
|
|
// the driver's inf specifies these settings. If it does not,
|
|
// then if the registery tells us to override the capture filter's
|
|
// default settings, then attempt to set it to 176x144 YUV.
|
|
//
|
|
// This is the order we attempt to set preferences:
|
|
//
|
|
// Preferred Settings --> If don't exist --> Set to Driver Default if within MIN/MAX range
|
|
// --> otherwise attempt to set to MINIMUM width/height.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
GUID *pDefaultMediaSubType = &m_pVideoProperties->pMediaType->subtype;
|
|
LONG lDefaultWidth = m_pVideoProperties->pVideoInfoHeader->bmiHeader.biWidth;
|
|
LONG lDefaultHeight = m_pVideoProperties->pVideoInfoHeader->bmiHeader.biHeight;
|
|
|
|
//
|
|
// Validate default values are valid.
|
|
//
|
|
if ((lDefaultWidth <= MIN_VIDEO_WIDTH) || (lDefaultHeight <= MIN_VIDEO_HEIGHT))
|
|
{
|
|
lDefaultWidth = MIN_VIDEO_WIDTH;
|
|
lDefaultHeight = MIN_VIDEO_HEIGHT;
|
|
}
|
|
else if ((lDefaultWidth > MAX_VIDEO_WIDTH) || (lDefaultHeight > MAX_VIDEO_HEIGHT))
|
|
{
|
|
lDefaultWidth = MIN_VIDEO_WIDTH;
|
|
lDefaultHeight = MIN_VIDEO_HEIGHT;
|
|
}
|
|
|
|
//
|
|
// If a preferred media subtype exists, use it, otherwise, use the
|
|
// default specified by the capture filter.
|
|
//
|
|
if (m_pVideoProperties->PreferredSettingsMask & PREFERRED_SETTING_MASK_MEDIASUBTYPE)
|
|
{
|
|
pMediaSubType = &m_pVideoProperties->PreferredMediaSubType;
|
|
DBG_TRC(("Settings: Using preferred media subtype -> dump of actual type is below"));
|
|
}
|
|
else
|
|
{
|
|
pMediaSubType = pDefaultMediaSubType;
|
|
DBG_TRC(("Settings: Using default media subtype, no preferred media subtype "
|
|
"found -> dump of actual type is below"));
|
|
}
|
|
|
|
//
|
|
// If the default width and height exist, use it, otherwise use the
|
|
// default width and height, provided they are valid values
|
|
//
|
|
if (m_pVideoProperties->PreferredSettingsMask & PREFERRED_SETTING_MASK_VIDEO_WIDTH_HEIGHT)
|
|
{
|
|
lWidth = m_pVideoProperties->PreferredWidth;
|
|
lHeight = m_pVideoProperties->PreferredHeight;
|
|
|
|
//
|
|
// Validate preferred settings are valid. If they are not, then
|
|
// set to default value.
|
|
//
|
|
if ((lWidth < MIN_VIDEO_WIDTH) ||
|
|
(lHeight < MIN_VIDEO_HEIGHT) ||
|
|
(lWidth > MAX_VIDEO_WIDTH) ||
|
|
(lHeight > MAX_VIDEO_HEIGHT))
|
|
{
|
|
DBG_TRC(("Settings: Using default video width and height, preferred settings were invalid "
|
|
"-> Invalid Width = %lu, Invalid Height = %lu", lWidth, lHeight));
|
|
|
|
lWidth = lDefaultWidth;
|
|
lHeight = lDefaultHeight;
|
|
}
|
|
else
|
|
{
|
|
DBG_TRC(("Settings: Using preferred settings of video width and height "
|
|
"-> dump of actual size is below"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lWidth = lDefaultWidth;
|
|
lHeight = lDefaultHeight;
|
|
}
|
|
|
|
hr = CDShowUtil::SetPreferredVideoFormat(pCapturePin,
|
|
pMediaSubType,
|
|
lWidth,
|
|
lHeight,
|
|
m_pVideoProperties);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
DBG_TRC(("Failed to set width = '%lu' and height = '%lu', "
|
|
"attempting to set it to its default settings of "
|
|
"width = '%lu', height = '%lu'",
|
|
lWidth, lHeight, lDefaultWidth, lDefaultHeight));
|
|
|
|
hr = CDShowUtil::SetPreferredVideoFormat(pCapturePin,
|
|
pDefaultMediaSubType,
|
|
lDefaultWidth,
|
|
lDefaultHeight,
|
|
m_pVideoProperties);
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
hr = S_OK;
|
|
DBG_WRN(("Failed on all attempts to set the preferrences of "
|
|
"the video properties (MediaSubType, width, height). "
|
|
"Attempting to continue anyway"));
|
|
}
|
|
}
|
|
|
|
//
|
|
// 6. Attempt to set the frame rate to 30 frames per second. If this
|
|
// fails for some reason, try 15 frames per second. If that fails,
|
|
// then just accept the default and keep going.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LONG lDefaultFrameRate = m_pVideoProperties->dwFrameRate;
|
|
|
|
if (lDefaultFrameRate < BACKUP_FRAME_RATE)
|
|
{
|
|
lDefaultFrameRate = PREFERRED_FRAME_RATE;
|
|
}
|
|
|
|
if (m_pVideoProperties->PreferredSettingsMask & PREFERRED_SETTING_MASK_VIDEO_FRAMERATE)
|
|
{
|
|
lFrameRate = m_pVideoProperties->PreferredFrameRate;
|
|
|
|
if (lFrameRate < BACKUP_FRAME_RATE)
|
|
{
|
|
lFrameRate = lDefaultFrameRate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lFrameRate = PREFERRED_FRAME_RATE;
|
|
}
|
|
|
|
hr = CDShowUtil::SetFrameRate(pCapturePin,
|
|
lFrameRate,
|
|
m_pVideoProperties);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
DBG_WRN(("WARNING: Failed to set frame rate to %lu. "
|
|
"This is not fatal, attempting to set it to %lu, "
|
|
"hr = 0x%08lx",
|
|
lFrameRate,
|
|
BACKUP_FRAME_RATE,
|
|
hr));
|
|
|
|
hr = CDShowUtil::SetFrameRate(pCapturePin,
|
|
lDefaultFrameRate,
|
|
m_pVideoProperties);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
DBG_WRN(("WARNING: Failed to set frame rate to %lu. "
|
|
"This is not fatal, continuing to build graph, "
|
|
"hr = 0x%08lx", lDefaultFrameRate, hr));
|
|
}
|
|
}
|
|
|
|
//
|
|
// This is a nice to have, but if we can't then continue
|
|
// anyway, better low quality video than no video at all.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
|
|
//
|
|
// Set the picture and camera attributes to our preferred settings,
|
|
// if we can.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// If this camera supports setting the picture attributes, then
|
|
// lets make sure that we set the ones we are interested in.
|
|
//
|
|
// This uses the DShow IAMVideoProcAmp interface
|
|
//
|
|
if (m_pVideoProperties->bPictureAttributesUsed)
|
|
{
|
|
//
|
|
// Make sure we are outputing color video (as opposed to black and white)
|
|
//
|
|
hr = CDShowUtil::SetPictureAttribute(pCaptureFilter,
|
|
&m_pVideoProperties->ColorEnable,
|
|
(LONG) TRUE,
|
|
VideoProcAmp_Flags_Manual);
|
|
|
|
//
|
|
// Turn on backlight compensation, to get the best video we can.
|
|
//
|
|
hr = CDShowUtil::SetPictureAttribute(pCaptureFilter,
|
|
&m_pVideoProperties->BacklightCompensation,
|
|
(LONG) TRUE,
|
|
VideoProcAmp_Flags_Manual);
|
|
|
|
//
|
|
// Make sure white balance is set to auto
|
|
//
|
|
hr = CDShowUtil::SetPictureAttribute(pCaptureFilter,
|
|
&m_pVideoProperties->WhiteBalance,
|
|
m_pVideoProperties->WhiteBalance.lCurrentValue,
|
|
VideoProcAmp_Flags_Auto);
|
|
}
|
|
|
|
//
|
|
// If the camera supports setting the camera attributes, then set the
|
|
// camera attributes we are interested in.
|
|
//
|
|
if (m_pVideoProperties->bCameraAttributesUsed)
|
|
{
|
|
//
|
|
// Turn on auto exposure.
|
|
//
|
|
hr = CDShowUtil::SetCameraAttribute(pCaptureFilter,
|
|
&m_pVideoProperties->Exposure,
|
|
m_pVideoProperties->Exposure.lCurrentValue,
|
|
CameraControl_Flags_Auto);
|
|
}
|
|
|
|
hr = S_OK;
|
|
}
|
|
|
|
|
|
//
|
|
// Dump the video properties
|
|
//
|
|
CDShowUtil::MyDumpVideoProperties(m_pVideoProperties);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*ppCapturePin = pCapturePin;
|
|
(*ppCapturePin)->AddRef();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// ConnectFilters
|
|
//
|
|
// This function connects the
|
|
// capture filter's still pin
|
|
// or it's capture pin to the
|
|
// color space converter. It
|
|
// then connects the color space
|
|
// converter to the WIA Stream
|
|
// snapshot filter. Last, it
|
|
// renders the WIA Stream Snapshot
|
|
// filter to bring in any remaining
|
|
// required filters (such as the
|
|
// video renderer)
|
|
//
|
|
HRESULT CPreviewGraph::ConnectFilters(IGraphBuilder *pGraphBuilder,
|
|
IPin *pMediaSourceOutputPin,
|
|
IBaseFilter *pColorSpaceFilter,
|
|
IBaseFilter *pWiaFilter,
|
|
IBaseFilter *pVideoRenderer)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CComPtr<IPin> pOutputPinToConnect;
|
|
|
|
ASSERT(pGraphBuilder != NULL);
|
|
ASSERT(pMediaSourceOutputPin != NULL);
|
|
ASSERT(pVideoRenderer != NULL);
|
|
|
|
if ((pGraphBuilder == NULL) ||
|
|
(pMediaSourceOutputPin == NULL) ||
|
|
(pVideoRenderer == NULL))
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters received a NULL pointer"));
|
|
}
|
|
|
|
pOutputPinToConnect = pMediaSourceOutputPin;
|
|
|
|
DBG_TRC(("CPreviewGraph::ConnectFilters, PinToRender is the "
|
|
"Capture Filter's Output Pin"));
|
|
|
|
if (pColorSpaceFilter)
|
|
{
|
|
CComPtr<IPin> pColorInputPin;
|
|
CComPtr<IPin> pColorOutputPin;
|
|
|
|
//
|
|
// Get the input pin on the color space filter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = CDShowUtil::GetPin(pColorSpaceFilter,
|
|
PINDIR_INPUT,
|
|
&pColorInputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to get the "
|
|
"color space converter's input pin"));
|
|
}
|
|
|
|
//
|
|
// Connect the Capture Filter's output pin to the color space
|
|
// converter's input pin.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = pGraphBuilder->Connect(pMediaSourceOutputPin,
|
|
pColorInputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to connect the "
|
|
"capture filter's pin to the color space converter pin"));
|
|
}
|
|
|
|
//
|
|
// Get the output pin on the color space converter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = CDShowUtil::GetPin(pColorSpaceFilter,
|
|
PINDIR_OUTPUT,
|
|
&pColorOutputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to get the "
|
|
"color space converter's output pin"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
pOutputPinToConnect = pColorOutputPin;
|
|
DBG_TRC(("CPreviewGraph::ConnectFilters, PinToRender is the "
|
|
"Color Space Converter's Output Pin"));
|
|
}
|
|
|
|
//
|
|
// If this fails, so what. Try to connect WIA Stream Snapshot
|
|
// filter anyway.
|
|
//
|
|
|
|
hr = S_OK;
|
|
}
|
|
|
|
if (pWiaFilter)
|
|
{
|
|
CComPtr<IPin> pWiaInputPin;
|
|
CComPtr<IPin> pWiaOutputPin;
|
|
|
|
//
|
|
// Get the input pin on the WIA Stream Snapshot filter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = CDShowUtil::GetPin(pWiaFilter,
|
|
PINDIR_INPUT,
|
|
&pWiaInputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to get the "
|
|
"WIA Stream Snapshot filter's input pin"));
|
|
}
|
|
|
|
//
|
|
// Connect the output pin of the color space converter to the input
|
|
// pin on the WIA Stream Snapshot filter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = pGraphBuilder->Connect(pOutputPinToConnect,
|
|
pWiaInputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to connect the "
|
|
"pin to render to the WIA Stream Snapshot "
|
|
"input pin"));
|
|
}
|
|
|
|
//
|
|
// Get the output pin on the WIA Stream Snapshot filter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = CDShowUtil::GetPin(pWiaFilter,
|
|
PINDIR_OUTPUT,
|
|
&pWiaOutputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to get the "
|
|
"WIA Stream Snapshot filter's output pin"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
pOutputPinToConnect = pWiaOutputPin;
|
|
DBG_TRC(("CPreviewGraph::ConnectFilters, PinToRender is the "
|
|
"WIA Stream Snapshot Filter's Output Pin"));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Render the output pin of the WIA Stream Snapshot filter.
|
|
// This completes the graph building process.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
CComPtr<IPin> pVideoRendererInputPin;
|
|
|
|
//
|
|
// Get the input pin on the WIA Stream Snapshot filter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = CDShowUtil::GetPin(pVideoRenderer,
|
|
PINDIR_INPUT,
|
|
&pVideoRendererInputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to get the "
|
|
"WIA Stream Snapshot filter's input pin"));
|
|
}
|
|
|
|
//
|
|
// Connect the output pin of the color space converter to the input
|
|
// pin on the WIA Stream Snapshot filter.
|
|
//
|
|
if (hr == S_OK)
|
|
{
|
|
hr = pGraphBuilder->Connect(pOutputPinToConnect,
|
|
pVideoRendererInputPin);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::ConnectFilters, failed to connect the "
|
|
"pin to render to the Video Renderer "
|
|
"input pin"));
|
|
}
|
|
}
|
|
|
|
pOutputPinToConnect = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////
|
|
// BuildPreviewGraph
|
|
//
|
|
// This builds the preview graph
|
|
// based on the device ID we
|
|
// pass it.
|
|
//
|
|
HRESULT CPreviewGraph::BuildPreviewGraph(IMoniker *pCaptureDeviceMoniker,
|
|
BOOL bStretchToFitParent)
|
|
{
|
|
DBG_FN("CPreviewGraph::BuildPreviewGraph");
|
|
|
|
ASSERT(pCaptureDeviceMoniker != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
CComPtr<IPin> pCapturePin;
|
|
|
|
if (pCaptureDeviceMoniker == NULL)
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("ERROR: CPreviewGraph::BuildPreviewGraph "
|
|
"received a NULL param"));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// This function will build one of three possible graphs.
|
|
//
|
|
// (1) The capture filter does NOT have a
|
|
// still pin on it (or if it does it is useless because
|
|
// it is not triggerable either via hardware, or programmatically)
|
|
//
|
|
// CaptureFilter(Capture Pin) -> Decoder -> Color Converter -> WIA StreamSnapshot -> Renderer
|
|
//
|
|
// (2) The capture filter has a still pin that is programmatically
|
|
// triggerable.
|
|
//
|
|
// CaptureFilter(CapturePin) -> Decoder -> Video Renderer
|
|
// (StillPin) -> Decoder -> Color Converter -> WIA StreamSnapshot -> Renderer
|
|
//
|
|
// (3) The capture filter has a still pin, but it is only
|
|
// triggered via external hardware button. In this case, if we
|
|
// trigger a snapshot programmatically, the image comes from
|
|
// the WIA Snapshot filter off of the Capture/Preview Pin.
|
|
// If the hardware button is pressed, the image comes from the
|
|
// WIA snapshot filter off of the StillPin.
|
|
//
|
|
// CaptureFilter(CapturePin) -> Decoder -> Color Converter -> WIA StreamSnapshot -> Renderer
|
|
// (StillPin) -> Decoder -> Color Converter -> WIA StreamSnapshot -> Renderer
|
|
//
|
|
//
|
|
DBG_TRC(("CPreviewGraph::BuildPreviewGraph - Starting to build preview "
|
|
"graph"));
|
|
|
|
//
|
|
// 1. Create the capture filter based on the device ID we were given.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CreateCaptureFilter(pCaptureDeviceMoniker,
|
|
&m_pCaptureFilter); // passed by ref
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"create capture filter"));
|
|
}
|
|
|
|
//
|
|
// 2. Create the DShow graph builder objects allowing us to
|
|
// manipulate the graph.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CDShowUtil::CreateGraphBuilder(&m_pCaptureGraphBuilder,
|
|
&m_pGraphBuilder);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"create DShow graph builder object"));
|
|
}
|
|
|
|
//
|
|
// 3. Add the capture filter to the graph.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = AddCaptureFilterToGraph(m_pCaptureFilter, &pCapturePin);
|
|
}
|
|
|
|
//
|
|
// 4. Get the Still Pin capabilities of the capture filter (if it has a
|
|
// still pin at all)
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = GetStillPinCaps(m_pCaptureFilter,
|
|
&m_pStillPin,
|
|
&m_pVideoControl,
|
|
&m_lStillPinCaps);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
//
|
|
// This is not an error condition, it simply means that the
|
|
// capture filter does not have a still pin on it. That's
|
|
// okay, we can deal with that below.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Render the preview/capture stream.
|
|
// ==================================
|
|
//
|
|
// If we don't have a still pin, or the still pin cannot be internally
|
|
// triggered, we build the following preview graph.
|
|
//
|
|
// CaptureFilter(Capture Pin) -> Decoder -> WIA StreamSnapshot -> Renderer
|
|
//
|
|
// If we do have a still pin, and it is internally triggerable, then
|
|
// we build the following preview graph (and add the still filter to the
|
|
// still pin)
|
|
//
|
|
// CaptureFilter(CapturePin) -> Decoder -> WIA StreamSnapshot -> Renderer
|
|
// (StillPin) -> Decoder -> WIA StreamSnapshot -> Renderer
|
|
//
|
|
|
|
//
|
|
// 5. If we don't have a still pin, or it is only externally triggerable
|
|
// then add the WIA StreamSnapshot Filter to the preview/capture pin.
|
|
//
|
|
if ((m_pStillPin == NULL) ||
|
|
(m_lStillPinCaps & VideoControlFlag_Trigger) == 0)
|
|
{
|
|
CComPtr<IBaseFilter> pWiaFilter;
|
|
CComPtr<IBaseFilter> pColorSpaceConverter;
|
|
CComPtr<IBaseFilter> pVideoRenderer;
|
|
|
|
DBG_TRC(("CPreviewGraph::BuildPreviewGraph, capture filter does NOT have "
|
|
"a still pin, image captures will be triggered "
|
|
"through the WIA Snapshot filter"));
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddColorConverterToGraph(L"Color Converter on Capture Pin Graph",
|
|
&pColorSpaceConverter);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add Color Converter to graph"));
|
|
|
|
//
|
|
// Even if this fails, we still may be able to successfully build
|
|
// a graph, so attempt to continue.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddStillFilterToGraph(L"Still Filter On Capture",
|
|
&pWiaFilter,
|
|
&m_pCapturePinSnapshot);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add 'Still Filter On Capture' to graph"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddVideoRendererToGraph(L"Video Renderer On Capture",
|
|
&pVideoRenderer);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add 'Video Renderer On Capture' to graph"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Connect as follows:
|
|
//
|
|
// Capture Filter --> Color Space Converter --> WIA Stream Snapshot Filter
|
|
//
|
|
hr = ConnectFilters(m_pGraphBuilder,
|
|
pCapturePin,
|
|
pColorSpaceConverter,
|
|
pWiaFilter,
|
|
pVideoRenderer);
|
|
}
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add still filter to graph off of the capture pin"));
|
|
}
|
|
else
|
|
{
|
|
CComPtr<IBaseFilter> pColorSpaceConverter;
|
|
CComPtr<IBaseFilter> pVideoRenderer;
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddColorConverterToGraph(L"Color Converter on Capture Pin Graph",
|
|
&pColorSpaceConverter);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add Color Converter to graph"));
|
|
|
|
//
|
|
// Even if this fails, we still should be able to build the graph,
|
|
// so continue.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddVideoRendererToGraph(L"Video Renderer On Capture",
|
|
&pVideoRenderer);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add 'Video Renderer On Capture' to graph"));
|
|
}
|
|
|
|
//
|
|
// Still Pin exists, and it is triggerable, so simply render
|
|
// the capture pin. We connect the Still Pin below
|
|
//
|
|
hr = ConnectFilters(m_pGraphBuilder,
|
|
pCapturePin,
|
|
pColorSpaceConverter,
|
|
NULL,
|
|
pVideoRenderer);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to connect "
|
|
"filters to render capture pin, therefore won't see video"));
|
|
}
|
|
|
|
CDShowUtil::MyDumpGraph(TEXT("Capture Graph before processing Still Pin (if exists)"),
|
|
m_pGraphBuilder);
|
|
|
|
//
|
|
// Render the Still Pin stream
|
|
// ===========================
|
|
//
|
|
// If we have a still pin, then add the still filter to the
|
|
// graph, and render the still pin. This will produce the
|
|
// following graph:
|
|
//
|
|
// CaptureFilter(StillPin) -> Decoder -> StillFilter -> Renderer (hidden)
|
|
|
|
//
|
|
// 6. Now add the WIA Stream Snapshot filter to the still pin if it
|
|
// exists.
|
|
//
|
|
if (SUCCEEDED(hr) && (m_pStillPin))
|
|
{
|
|
CComPtr<IBaseFilter> pWiaFilter;
|
|
CComPtr<IBaseFilter> pColorSpaceConverter;
|
|
CComPtr<IBaseFilter> pVideoRenderer;
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddColorConverterToGraph(L"Color Converter on Still Pin Graph",
|
|
&pColorSpaceConverter);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add Color Converter to graph"));
|
|
|
|
//
|
|
// This is not fatail if we fail. Ideally we would like it in
|
|
// here, but try going on anyway, in case we can succeed without
|
|
// this filter.
|
|
//
|
|
hr = S_OK;
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddStillFilterToGraph(L"Still filter on Still",
|
|
&pWiaFilter,
|
|
&m_pStillPinSnapshot);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to add "
|
|
"'Still filter on Still' filter to the graph. "
|
|
"Probably won't be able to capture still images"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = AddVideoRendererToGraph(L"Video Renderer On Still",
|
|
&pVideoRenderer);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"add 'Video Renderer On Still' to graph"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// Connect as follows:
|
|
//
|
|
// Capture Filter --> Color Space Converter --> WIA Stream Snapshot Filter
|
|
//
|
|
hr = ConnectFilters(m_pGraphBuilder,
|
|
m_pStillPin,
|
|
pColorSpaceConverter,
|
|
pWiaFilter,
|
|
pVideoRenderer);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"connect graph to Still Pin on Capture Filter. "
|
|
"This will prevent us from capturing still images"));
|
|
}
|
|
}
|
|
|
|
CDShowUtil::MyDumpGraph(TEXT("*** Complete Graph ***"), m_pGraphBuilder);
|
|
|
|
//
|
|
// 7. Turn off the graph's clock. We do this in case some frames are
|
|
// delivered late, at least they will still be delivered, the graph
|
|
// won't drop them. Since we don't have any sound, we can do this
|
|
// without worrying about losing sync with our sound.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = CDShowUtil::TurnOffGraphClock(m_pGraphBuilder);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
DBG_WRN(("CPreviewGraph::BuildPreviewGraph, failed to turn off the "
|
|
"graph clock. This is not fatal, continuing..., hr = 0x%lx",
|
|
hr));
|
|
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 8. Register ourselves with the WIA StreamSnapshot Filter so that
|
|
// a callback of ours will be called if a still image is available.
|
|
//
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// the graph is ready to run. Initialize still filter and
|
|
// register callback to get notification for new snapshots.
|
|
//
|
|
|
|
if (m_pCapturePinSnapshot)
|
|
{
|
|
m_pCapturePinSnapshot->SetSamplingSize(
|
|
CAPTURE_NUM_SAMPLES_TO_CACHE);
|
|
}
|
|
|
|
if (m_pStillPinSnapshot)
|
|
{
|
|
m_pStillPinSnapshot->SetSamplingSize(STILL_NUM_SAMPLES_TO_CACHE);
|
|
}
|
|
|
|
hr = m_StillProcessor.RegisterStillProcessor(m_pCapturePinSnapshot,
|
|
m_pStillPinSnapshot);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::BuildPreviewGraph, failed to "
|
|
"register our still processor's callback fn"));
|
|
}
|
|
|
|
//
|
|
// 9. Get the Video Renderer window off of the preview/capture pin.
|
|
// This will allow us to control the renderer position, size, etc.
|
|
//
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = InitVideoWindows(m_hwndParent,
|
|
m_pCaptureFilter,
|
|
&m_pPreviewVW,
|
|
bStretchToFitParent);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// TeardownPreviewGraph
|
|
//
|
|
HRESULT CPreviewGraph::TeardownPreviewGraph()
|
|
{
|
|
DBG_FN("CPreviewGraph::TeardownPreviewGraph");
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
m_pStillPin = NULL;
|
|
m_pVideoControl = NULL;
|
|
m_pStillPinSnapshot = NULL;
|
|
m_pCapturePinSnapshot = NULL;
|
|
|
|
if (m_pPreviewVW)
|
|
{
|
|
CDShowUtil::ShowVideo(FALSE, m_pPreviewVW);
|
|
CDShowUtil::SetVideoWindowParent(NULL, m_pPreviewVW, &m_lStyle);
|
|
|
|
m_pPreviewVW = NULL;
|
|
}
|
|
|
|
//
|
|
// Remove all the filters from the graph.
|
|
//
|
|
|
|
if (m_pGraphBuilder)
|
|
{
|
|
RemoveAllFilters();
|
|
}
|
|
|
|
CDShowUtil::MyDumpGraph(TEXT("Graph after removing all filters ")
|
|
TEXT("(should be empty)"),
|
|
m_pGraphBuilder);
|
|
|
|
m_pCaptureGraphBuilder = NULL;
|
|
m_pGraphBuilder = NULL;
|
|
m_pCaptureFilter = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// RemoveAllFilters
|
|
//
|
|
// Notice this function makes no
|
|
// attempt to disconnect each filter
|
|
// before removing it. According to
|
|
// MSDN, you do not need to disconnect
|
|
// a filter before removing it, you only
|
|
// need to ensure that the graph is stopped.
|
|
//
|
|
HRESULT CPreviewGraph::RemoveAllFilters()
|
|
{
|
|
ASSERT(m_pGraphBuilder != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
CComPtr<IEnumFilters> pEnum = NULL;
|
|
DWORD dwRefCount = 0;
|
|
BOOL bDone = FALSE;
|
|
|
|
if (m_pGraphBuilder == NULL)
|
|
{
|
|
hr = E_POINTER;
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::RemoveAllFilters, m_pGraphBuilder "
|
|
"is NULL, cannot remove filters"));
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = m_pGraphBuilder->EnumFilters(&pEnum);
|
|
}
|
|
|
|
if (pEnum)
|
|
{
|
|
// enumerate each filter.
|
|
while (!bDone)
|
|
{
|
|
CComPtr<IBaseFilter> pFilter = NULL;
|
|
DWORD dwNumFetched = 0;
|
|
|
|
//
|
|
// Notice we reset our enumeration on every iteration since
|
|
// the act of removing a filter from the graph may do some
|
|
// funny things, so we really want to always remove the first
|
|
// filter we get from the list until the list is empty.
|
|
//
|
|
hr = pEnum->Reset();
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = pEnum->Next(1, &pFilter, &dwNumFetched);
|
|
}
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
//
|
|
// This will disconnect the filter's pins and remove
|
|
// it from the graph. If this fails, we want to get out
|
|
// of the loop because otherwise we'll be in an endless loop
|
|
// since we failed to remove the filter, then we reset our
|
|
// enum and get the next filter, which will be this
|
|
// filter again.
|
|
//
|
|
hr = m_pGraphBuilder->RemoveFilter(pFilter);
|
|
|
|
CHECK_S_OK2(hr, ("CPreviewGraph::RemoveAllFilters, "
|
|
"RemoveFilter failed"));
|
|
|
|
//
|
|
// Release our ref count.
|
|
//
|
|
pFilter = NULL;
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
bDone = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// HandlePowerEvent
|
|
//
|
|
LRESULT CPreviewGraph::HandlePowerEvent(WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
LRESULT iReturn = TRUE;
|
|
|
|
if (wParam == PBT_APMQUERYSUSPEND)
|
|
{
|
|
if (GetState() != WIAVIDEO_NO_VIDEO)
|
|
{
|
|
iReturn = BROADCAST_QUERY_DENY;
|
|
}
|
|
}
|
|
|
|
return iReturn;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// CreateHiddenWindow
|
|
//
|
|
// Used to handle power management
|
|
// messages.
|
|
//
|
|
HRESULT CPreviewGraph::CreateHiddenWindow()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WNDCLASSEX wc = {0};
|
|
|
|
wc.style = 0;
|
|
wc.cbSize = sizeof(wc);
|
|
wc.lpfnWndProc = HiddenWndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = sizeof(this);
|
|
wc.hInstance = _Module.GetModuleInstance();
|
|
wc.hIcon = NULL;
|
|
wc.hIconSm = NULL;
|
|
wc.hCursor = 0;
|
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
|
|
wc.lpszMenuName = 0;
|
|
wc.lpszClassName = TEXT("WIAVIDEO_POWERMGMT");
|
|
|
|
RegisterClassEx(&wc);
|
|
|
|
m_hwndPowerMgmt = CreateWindowEx(0,
|
|
TEXT("WIAVIDEO_POWERMGMT"),
|
|
TEXT("WIAVIDEO_POWERMGMT"),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
_Module.GetModuleInstance(),
|
|
this);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////
|
|
// DestroyHiddenWindow
|
|
//
|
|
HRESULT CPreviewGraph::DestroyHiddenWindow()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (m_hwndPowerMgmt)
|
|
{
|
|
SendMessage(m_hwndPowerMgmt, WM_CLOSE, 0, 0);
|
|
}
|
|
|
|
m_hwndPowerMgmt = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// HiddenWndProc
|
|
//
|
|
// Static Function
|
|
//
|
|
LRESULT CALLBACK CPreviewGraph::HiddenWndProc(HWND hwnd,
|
|
UINT uiMessage,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
LRESULT iReturn = 0;
|
|
|
|
switch (uiMessage)
|
|
{
|
|
case WM_CREATE:
|
|
{
|
|
CREATESTRUCT *pCreateInfo = reinterpret_cast<CREATESTRUCT*>(lParam);
|
|
if (pCreateInfo)
|
|
{
|
|
SetWindowLongPtr(hwnd, 0, reinterpret_cast<LONG_PTR>(pCreateInfo->lpCreateParams));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_POWERBROADCAST:
|
|
{
|
|
CPreviewGraph *pPreviewGraph = NULL;
|
|
|
|
pPreviewGraph = reinterpret_cast<CPreviewGraph*>(GetWindowLongPtr(hwnd, 0));
|
|
|
|
if (pPreviewGraph)
|
|
{
|
|
iReturn = pPreviewGraph->HandlePowerEvent(wParam, lParam);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
DestroyWindow(hwnd);
|
|
break;
|
|
|
|
default:
|
|
iReturn = DefWindowProc(hwnd,
|
|
uiMessage,
|
|
wParam,
|
|
lParam);
|
|
break;
|
|
}
|
|
|
|
return iReturn;
|
|
}
|