2605 lines
68 KiB
C++
2605 lines
68 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
|
// DSUtil.h: DirectShow helper functions.
|
|
//
|
|
// 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.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Conventions:
|
|
//
|
|
// Functions named "IsX" return true or false.
|
|
//
|
|
// Functions named "FindX" enumerate over a collection and return the first
|
|
// matching instance.
|
|
|
|
#pragma once
|
|
|
|
#include <dshow.h>
|
|
#include <strsafe.h>
|
|
#include <assert.h>
|
|
|
|
#ifndef ASSERT
|
|
#define ASSERT assert
|
|
#endif
|
|
|
|
#ifndef SAFE_RELEASE
|
|
#define SAFE_RELEASE(x) if (x) { x->Release(); x = NULL; }
|
|
#endif
|
|
|
|
#ifndef CHECK_HR
|
|
#define CHECK_HR(hr) if (FAILED(hr)) { goto done; }
|
|
#endif
|
|
|
|
/*
|
|
Pin and Filter Functions
|
|
-------------------
|
|
GetPinCategory
|
|
GetPinMediaType
|
|
FilterSupportsPropertyPage
|
|
FindUnconnectedPin
|
|
FindConnectedPin
|
|
FindPinByCategory
|
|
FindPinByIndex
|
|
FindPinByMajorType
|
|
FindPinByName
|
|
FindPinInterface
|
|
FindMatchingPin
|
|
IsPinConnected
|
|
IsPinDirection
|
|
IsPinUnconnected
|
|
IsSourceFilter
|
|
IsRenderer
|
|
ShowFilterPropertyPage
|
|
|
|
Graph Building Functions
|
|
-----------------------
|
|
AddFilterByCLSID
|
|
AddFilterFromMoniker
|
|
AddSourceFilter
|
|
AddVMR9Filter
|
|
AddWriterFilter
|
|
ConnectFilters
|
|
CreateKernelFilter
|
|
DisconnectPin
|
|
FindFilterInterface
|
|
FindGraphInterface
|
|
GetNextFilter
|
|
GetConnectedFilter
|
|
RemoveFilter
|
|
RemoveFiltersDownstream
|
|
RemoveUnconnectedFilters
|
|
RenderFileToVideoRenderer
|
|
|
|
Media Type Functions
|
|
--------------------
|
|
CreatePCMAudioType
|
|
CreateRGBVideoType
|
|
DeleteMediaType
|
|
FreeMediaType
|
|
CopyFormatBlock
|
|
|
|
GraphEdit Functions
|
|
-------------------
|
|
AddGraphToRot
|
|
LoadGraphFile
|
|
RemoveGraphFromRot
|
|
SaveGraphFile
|
|
|
|
Misc Functions
|
|
--------------
|
|
FramesPerSecToFrameLength
|
|
LetterBoxRect
|
|
MsecToRefTime
|
|
RectWidth
|
|
RectHeight
|
|
RefTimeToMsec
|
|
RefTimeToSeconds
|
|
SecondsToRefTime
|
|
|
|
*/
|
|
|
|
|
|
|
|
const LONGLONG ONE_SECOND = 10000000; // One second, in 100-nanosecond units.
|
|
const LONGLONG ONE_MSEC = ONE_SECOND / 10000; // One millisecond, in 100-nanosecond units
|
|
|
|
// Directions for filter graph data flow.
|
|
enum GraphDirection
|
|
{
|
|
UPSTREAM, DOWNSTREAM
|
|
};
|
|
|
|
|
|
// Forward declares
|
|
|
|
void _FreeMediaType(AM_MEDIA_TYPE& mt);
|
|
void _DeleteMediaType(AM_MEDIA_TYPE *pmt);
|
|
HRESULT RemoveFiltersDownstream(IGraphBuilder *pGraph, IBaseFilter *pFilter);
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
|
|
Pin Query Functions - Test pins for various things
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: GetPinCategory
|
|
// Desc: Returns the category of a pin
|
|
//
|
|
// Note: Pin categories are used by some kernel-mode filters to
|
|
// distinguish different outputs. (e.g, capture and preview)
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT GetPinCategory(IPin *pPin, GUID *pPinCategory)
|
|
{
|
|
if (pPin == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if (pPinCategory == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD cbReturned = 0;
|
|
IKsPropertySet *pKs = NULL;
|
|
|
|
CHECK_HR(hr = pPin->QueryInterface(IID_IKsPropertySet, (void**)&pKs));
|
|
|
|
// Try to retrieve the pin category.
|
|
CHECK_HR(hr = pKs->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0,
|
|
pPinCategory, sizeof(GUID), &cbReturned));
|
|
|
|
// If this succeeded, pPinCategory now contains the category GUID.
|
|
|
|
|
|
done:
|
|
SAFE_RELEASE(pKs);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: GetPinMediaType
|
|
// Desc: Given a pin, find a preferred media type
|
|
//
|
|
// pPin Pointer to the pin
|
|
// majorType Preferred major type (GUID_NULL = don't care)
|
|
// subType Preferred subtype (GUID_NULL = don't care)
|
|
// formatType Preferred format type (GUID_NULL = don't care)
|
|
// ppmt Receives a pointer to the media type. Can be NULL.
|
|
//
|
|
// Note: If you want to check whether a pin supports a desired media type,
|
|
// but do not need the format details, set ppmt to NULL.
|
|
//
|
|
// If ppmt is not NULL and the method succeeds, the caller must
|
|
// delete the media type, including the format block. (Use the
|
|
// DeleteMediaType function.)
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT GetPinMediaType(
|
|
IPin *pPin, // pointer to the pin
|
|
REFGUID majorType, // desired major type, or GUID_NULL = don't care
|
|
REFGUID subType, // desired subtype, or GUID_NULL = don't care
|
|
REFGUID formatType, // desired format type, of GUID_NULL = don't care
|
|
AM_MEDIA_TYPE **ppmt // Receives a pointer to the media type. (Can be NULL)
|
|
)
|
|
{
|
|
if (!pPin)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
IEnumMediaTypes *pEnum = NULL;
|
|
AM_MEDIA_TYPE *pmt = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
bool bFound = false;
|
|
|
|
CHECK_HR(hr = pPin->EnumMediaTypes(&pEnum));
|
|
|
|
while (hr = pEnum->Next(1, &pmt, NULL), hr == S_OK)
|
|
{
|
|
if ((majorType == GUID_NULL) || (majorType == pmt->majortype))
|
|
{
|
|
if ((subType == GUID_NULL) || (subType == pmt->subtype))
|
|
{
|
|
if ((formatType == GUID_NULL) || (formatType == pmt->formattype))
|
|
{
|
|
// Found a match.
|
|
if (ppmt)
|
|
{
|
|
*ppmt = pmt; // Return it to the caller
|
|
}
|
|
else
|
|
{
|
|
_DeleteMediaType(pmt);
|
|
}
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
_DeleteMediaType(pmt);
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pEnum);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (!bFound)
|
|
{
|
|
hr = VFW_E_NOT_FOUND;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: IsPinConnected
|
|
// Desc: Query whether a pin is connected to another pin.
|
|
//
|
|
// Note: If you need to get the other pin, use IPin::ConnectedTo.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT IsPinConnected(IPin *pPin, BOOL *pResult)
|
|
{
|
|
if (pPin == NULL || pResult == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
IPin *pTmp = NULL;
|
|
HRESULT hr = pPin->ConnectedTo(&pTmp);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = TRUE;
|
|
}
|
|
else if (hr == VFW_E_NOT_CONNECTED)
|
|
{
|
|
// The pin is not connected. This is not an error for our purposes.
|
|
*pResult = FALSE;
|
|
hr = S_OK;
|
|
}
|
|
|
|
SAFE_RELEASE(pTmp);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: IsPinUnconnected
|
|
// Desc: Query whether a pin is NOT connected to another pin.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT IsPinUnconnected(IPin *pPin, BOOL *pResult)
|
|
{
|
|
// Check if the pin connected.
|
|
HRESULT hr = IsPinConnected(pPin, pResult);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Reverse the result.
|
|
*pResult = !(*pResult);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: IsPinDirection
|
|
// Desc: Query whether a pin has a specified direction (input / output)
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT IsPinDirection(IPin *pPin, PIN_DIRECTION dir, BOOL *pResult)
|
|
{
|
|
if (pPin == NULL || pResult == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
PIN_DIRECTION pinDir;
|
|
HRESULT hr = pPin->QueryDirection(&pinDir);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = (pinDir == dir);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
|
|
Function objects
|
|
|
|
These are used in some of the FindXXXX functions.
|
|
|
|
**********************************************************************/
|
|
|
|
// MatchPinName:
|
|
// Function object to match a pin by name.
|
|
struct MatchPinName
|
|
{
|
|
const WCHAR *m_wszName;
|
|
|
|
MatchPinName(const WCHAR *wszName)
|
|
{
|
|
m_wszName = wszName;
|
|
}
|
|
|
|
HRESULT operator()(IPin *pPin, BOOL *pResult)
|
|
{
|
|
assert(pResult != NULL);
|
|
|
|
PIN_INFO PinInfo;
|
|
HRESULT hr = pPin->QueryPinInfo(&PinInfo);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
PinInfo.pFilter->Release();
|
|
|
|
// TODO: Use Strsafe
|
|
if (wcscmp(m_wszName, PinInfo.achName) == 0)
|
|
{
|
|
*pResult = TRUE;
|
|
}
|
|
else
|
|
{
|
|
*pResult = FALSE;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
};
|
|
|
|
|
|
// MatchPinDirectionAndConnection
|
|
// Function object to match a pin by direction and connection
|
|
|
|
struct MatchPinDirectionAndConnection
|
|
{
|
|
BOOL m_bShouldBeConnected;
|
|
PIN_DIRECTION m_direction;
|
|
|
|
|
|
MatchPinDirectionAndConnection(PIN_DIRECTION direction, BOOL bShouldBeConnected)
|
|
: m_bShouldBeConnected(bShouldBeConnected), m_direction(direction)
|
|
{
|
|
}
|
|
|
|
HRESULT operator()(IPin *pPin, BOOL *pResult)
|
|
{
|
|
assert(pResult != NULL);
|
|
|
|
BOOL bMatch = FALSE;
|
|
BOOL bIsConnected = FALSE;
|
|
|
|
HRESULT hr = IsPinConnected(pPin, &bIsConnected);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (bIsConnected == m_bShouldBeConnected)
|
|
{
|
|
hr = IsPinDirection(pPin, m_direction, &bMatch);
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = bMatch;
|
|
}
|
|
return hr;
|
|
}
|
|
};
|
|
|
|
|
|
// MatchPinConnection
|
|
// Function object to match a pin connection status
|
|
|
|
struct MatchPinConnection
|
|
{
|
|
BOOL m_bShouldBeConnected;
|
|
|
|
MatchPinConnection(BOOL bShouldBeConnected)
|
|
: m_bShouldBeConnected(bShouldBeConnected)
|
|
{
|
|
}
|
|
|
|
HRESULT operator()(IPin *pPin, BOOL *pResult)
|
|
{
|
|
assert(pResult != NULL);
|
|
|
|
BOOL bIsConnected = FALSE;
|
|
|
|
HRESULT hr = IsPinConnected(pPin, &bIsConnected);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = (bIsConnected == m_bShouldBeConnected);
|
|
}
|
|
return hr;
|
|
}
|
|
};
|
|
|
|
// MatchPinDirectionAndCategory
|
|
// Function object to match a pin by direction and category.
|
|
struct MatchPinDirectionAndCategory
|
|
{
|
|
const GUID* m_pCategory;
|
|
PIN_DIRECTION m_direction;
|
|
|
|
MatchPinDirectionAndCategory(PIN_DIRECTION direction, REFGUID guidCategory)
|
|
: m_direction(direction), m_pCategory(&guidCategory)
|
|
{
|
|
}
|
|
|
|
HRESULT operator()(IPin *pPin, BOOL *pResult)
|
|
{
|
|
assert(pResult != NULL);
|
|
|
|
BOOL bMatch = FALSE;
|
|
GUID category;
|
|
|
|
HRESULT hr = IsPinDirection(pPin, m_direction, &bMatch);
|
|
|
|
if (SUCCEEDED(hr) && bMatch)
|
|
{
|
|
hr = GetPinCategory(pPin, &category);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bMatch = (category == *m_pCategory);
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = bMatch;
|
|
}
|
|
return hr;
|
|
}
|
|
};
|
|
|
|
|
|
struct MatchPinMediaType
|
|
{
|
|
GUID m_majorType;
|
|
MatchPinDirectionAndConnection m_match1;
|
|
|
|
MatchPinMediaType(REFGUID majorType, PIN_DIRECTION dir, BOOL bShouldBeConnected)
|
|
: m_majorType(majorType), m_match1(dir, bShouldBeConnected)
|
|
{
|
|
}
|
|
|
|
HRESULT operator()(IPin *pPin, BOOL *pResult)
|
|
{
|
|
assert(pResult != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL bMatch = FALSE;
|
|
|
|
// First try to match on direction and connection status.
|
|
hr = m_match1(pPin, &bMatch);
|
|
|
|
// Next, try to match media types.
|
|
if (SUCCEEDED(hr) && bMatch)
|
|
{
|
|
// For a connected pin, try to match on the
|
|
// media type for the pin connection. Otherwise,
|
|
// try to match on the preferred media type.
|
|
|
|
const BOOL bConnected = m_match1.m_bShouldBeConnected;
|
|
|
|
if (bConnected)
|
|
{
|
|
AM_MEDIA_TYPE mt = { 0 };
|
|
|
|
hr = pPin->ConnectionMediaType(&mt);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bMatch = (mt.majortype == m_majorType);
|
|
_FreeMediaType(mt);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = GetPinMediaType(pPin, m_majorType, GUID_NULL, GUID_NULL, NULL);
|
|
|
|
if (hr == VFW_E_NOT_FOUND)
|
|
{
|
|
bMatch = FALSE;
|
|
hr = S_OK; // Not a failure case, just no match.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = bMatch;
|
|
}
|
|
return hr;
|
|
}
|
|
};
|
|
|
|
/**************************************************************************
|
|
|
|
Pin Searching Functions
|
|
|
|
These functions search a filter for a pin that matches some set of
|
|
criteria. They return the first pin that matches, or VFW_E_NOT_FOUND.
|
|
|
|
**************************************************************************/
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindPinInterface
|
|
// Desc: Search a filter for a pin that exposes a specified interface.
|
|
// (Returns the first instance found.)
|
|
//
|
|
// pFilter Pointer to the filter to search.
|
|
// iid IID of the interface.
|
|
// ppUnk Receives the interface pointer.
|
|
// Q Address of an ATL smart pointer.
|
|
//
|
|
// Note: This function returns the first instance that it finds.
|
|
// If no pin is found, the function returns VFW_E_NOT_FOUND.
|
|
// The templated version deduces the IID.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindPinInterface(
|
|
IBaseFilter *pFilter, // Pointer to the filter to search.
|
|
REFGUID iid, // IID of the interface.
|
|
void **ppUnk) // Receives the interface pointer.
|
|
{
|
|
if (!pFilter || !ppUnk)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
bool bFound = false;
|
|
|
|
IEnumPins *pEnum = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
CHECK_HR(hr = pFilter->EnumPins(&pEnum));
|
|
|
|
// Query every pin for the interface.
|
|
while (S_OK == pEnum->Next(1, &pPin, 0))
|
|
{
|
|
hr = pPin->QueryInterface(iid, ppUnk);
|
|
SAFE_RELEASE(pPin);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pPin);
|
|
|
|
return (bFound ? S_OK : VFW_E_NOT_FOUND);
|
|
}
|
|
template <class Q>
|
|
HRESULT FindPinInterface(IBaseFilter *pFilter, Q** pp)
|
|
{
|
|
return FindPinInterface(pFilter, __uuidof(Q), (void**)pp);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindMatchingPin (template)
|
|
// Desc: Return the first pin on a filter that matches a caller-supplied
|
|
// function or function object
|
|
//
|
|
// FN must be either
|
|
// (a) A function with the signature HRESULT (IPin*, BOOL *)
|
|
// (b) A class that implements HRESULT operator()(IPin*, BOOL *)
|
|
//
|
|
// FindMatchingPin halts if FN fails or returns TRUE
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
template <class PinPred>
|
|
HRESULT FindMatchingPin(IBaseFilter *pFilter, PinPred FN, IPin **ppPin)
|
|
{
|
|
IEnumPins *pEnum = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
HRESULT hr = pFilter->EnumPins(&pEnum);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
BOOL bFound = FALSE;
|
|
while (S_OK == pEnum->Next(1, &pPin, NULL))
|
|
{
|
|
hr = FN(pPin, &bFound);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
pPin->Release();
|
|
break;
|
|
}
|
|
if (bFound)
|
|
{
|
|
*ppPin = pPin;
|
|
break;
|
|
}
|
|
|
|
pPin->Release();
|
|
}
|
|
|
|
pEnum->Release();
|
|
|
|
if (!bFound)
|
|
{
|
|
hr = VFW_E_NOT_FOUND;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindPinByIndex
|
|
// Desc: Return the Nth pin with the specified pin direction.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindPinByIndex(IBaseFilter *pFilter, PIN_DIRECTION PinDir,
|
|
UINT nIndex, IPin **ppPin)
|
|
{
|
|
if (!pFilter || !ppPin)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
bool bFound = false;
|
|
UINT count = 0;
|
|
|
|
IEnumPins *pEnum = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
CHECK_HR(hr = pFilter->EnumPins(&pEnum));
|
|
|
|
while (S_OK == (hr = pEnum->Next(1, &pPin, NULL)))
|
|
{
|
|
PIN_DIRECTION ThisDir;
|
|
CHECK_HR(hr = pPin->QueryDirection(&ThisDir));
|
|
|
|
if (ThisDir == PinDir)
|
|
{
|
|
if (nIndex == count)
|
|
{
|
|
*ppPin = pPin; // return to caller
|
|
(*ppPin)->AddRef();
|
|
bFound = true;
|
|
break;
|
|
}
|
|
count++;
|
|
}
|
|
SAFE_RELEASE(pPin);
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pPin);
|
|
SAFE_RELEASE(pEnum);
|
|
|
|
return (bFound ? S_OK : VFW_E_NOT_FOUND);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindUnconnectedPin
|
|
// Desc: Return the first unconnected input pin or output pin.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindUnconnectedPin(
|
|
IBaseFilter *pFilter, // Pointer to the filter.
|
|
PIN_DIRECTION PinDir, // Direction of the pin to find.
|
|
IPin **ppPin // Receives a pointer to the pin.
|
|
)
|
|
{
|
|
return FindMatchingPin(pFilter, MatchPinDirectionAndConnection(PinDir, FALSE), ppPin);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindConnectedPin
|
|
// Desc: Return the first connected input pin or output pin
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindConnectedPin(
|
|
IBaseFilter *pFilter, // Pointer to the filter.
|
|
PIN_DIRECTION PinDir, // Direction of the pin to find.
|
|
IPin **ppPin // Receives a pointer to the pin.
|
|
)
|
|
{
|
|
return FindMatchingPin(pFilter, MatchPinDirectionAndConnection(PinDir, TRUE), ppPin);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindPinByCategory
|
|
// Desc: Find the first pin that matches a specified pin category
|
|
// and direction.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindPinByCategory(
|
|
IBaseFilter *pFilter, // Pointer to the filter.
|
|
REFGUID guidCategory, // Category GUID
|
|
PIN_DIRECTION PinDir, // Pin direction to match.
|
|
IPin **ppPin
|
|
)
|
|
{
|
|
return FindMatchingPin(pFilter, MatchPinDirectionAndCategory(PinDir, guidCategory), ppPin);
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindPinByName
|
|
// Desc: Find a pin by name.
|
|
//
|
|
// Note: Generally, you should find pins by direction, media type,
|
|
// and/or pin category. However, in some cases you may need to
|
|
// find a pin by name.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindPinByName(IBaseFilter *pFilter, const WCHAR *wszName, IPin **ppPin)
|
|
{
|
|
if (!pFilter || !wszName || !ppPin)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
// Verify that wszName is not longer than MAX_PIN_NAME
|
|
size_t cch;
|
|
HRESULT hr = StringCchLengthW(wszName, MAX_PIN_NAME, &cch);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = FindMatchingPin(pFilter, MatchPinName(wszName), ppPin);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindPinByMajorType
|
|
// Desc: Find a pin that matches a major media type GUID.
|
|
//
|
|
// This function also matches my pin direction and pin connection
|
|
// status. Hypothetically, this function could allow "don't care"
|
|
// values for these. But in my experience, you generally know this
|
|
// information for real-world uses (eg., if you are building the graph,
|
|
// versus finding pins on a completed graph).
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindPinByMajorType(
|
|
IBaseFilter *pFilter, // Pointer to the filter.
|
|
REFGUID majorType, // Major media type.
|
|
PIN_DIRECTION PinDir, // Pin direction to match.
|
|
BOOL bConnected, // If TRUE, look for connected pins.
|
|
// Otherwise, look for unconnected pins.
|
|
IPin **ppPin // Receives a pointer to the pin.
|
|
)
|
|
{
|
|
if (!pFilter || !ppPin)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = FindMatchingPin(pFilter, MatchPinMediaType(majorType, PinDir, bConnected), ppPin);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/**********************************************************************
|
|
|
|
Filter Query Functions - Test filters for various conditions.
|
|
|
|
**********************************************************************/
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: IsSourceFilter
|
|
// Desc: Query whether a filter is a source filter.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT IsSourceFilter(IBaseFilter *pFilter, BOOL *pResult)
|
|
{
|
|
if (pFilter == NULL || pResult == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
IAMFilterMiscFlags *pFlags = NULL;
|
|
IFileSourceFilter *pFileSrc = NULL;
|
|
|
|
BOOL bIsSource = FALSE;
|
|
|
|
// If the filter exposes the IAMFilterMiscFlags interface, we use
|
|
// IAMFilterMiscFlags::GetMiscFlags to test if this is a source filter.
|
|
|
|
// Otherwise, it is a source filter if it exposes IFileSourceFilter.
|
|
|
|
// First try IAMFilterMiscFlags
|
|
HRESULT hr = pFilter->QueryInterface(IID_IAMFilterMiscFlags, (void**)&pFlags);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
|
|
ULONG flags = pFlags->GetMiscFlags();
|
|
if (flags & AM_FILTER_MISC_FLAGS_IS_SOURCE)
|
|
{
|
|
bIsSource = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Next, look for IFileSourceFilter.
|
|
hr = pFilter->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSrc);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bIsSource = TRUE;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = bIsSource;
|
|
}
|
|
|
|
SAFE_RELEASE(pFlags);
|
|
SAFE_RELEASE(pFileSrc);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: IsRenderer
|
|
// Desc: Query whether a filter is a renderer filter.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT IsRenderer(IBaseFilter *pFilter, BOOL *pResult)
|
|
{
|
|
if (pFilter == NULL || pResult == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL bIsRenderer = FALSE;
|
|
|
|
IAMFilterMiscFlags *pFlags = NULL;
|
|
IEnumPins *pEnum = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
|
|
// First try IAMFilterMiscFlags.
|
|
hr = pFilter->QueryInterface(IID_IAMFilterMiscFlags, (void**)&pFlags);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ULONG flags = pFlags->GetMiscFlags();
|
|
if (flags & AM_FILTER_MISC_FLAGS_IS_RENDERER)
|
|
{
|
|
bIsRenderer = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!bIsRenderer)
|
|
{
|
|
// Look for the following conditions:
|
|
|
|
// 1) Zero output pins AND at least 1 unmapped input pin
|
|
// - or -
|
|
// 2) At least 1 rendered input pin.
|
|
|
|
// definitions:
|
|
// unmapped input pin = IPin::QueryInternalConnections returns E_NOTIMPL
|
|
// rendered input pin = IPin::QueryInternalConnections returns "0" slots
|
|
|
|
// These cases are somewhat obscure and probably don't apply to many filters
|
|
// that actually exist.
|
|
|
|
hr = pFilter->EnumPins(&pEnum);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bool bFoundRenderedInputPin = false;
|
|
bool bFoundUnmappedInputPin = false;
|
|
bool bFoundOuputPin = false;
|
|
|
|
while (pEnum->Next(1, &pPin, NULL) == S_OK)
|
|
{
|
|
BOOL bIsOutput = FALSE;
|
|
hr = IsPinDirection(pPin, PINDIR_OUTPUT, &bIsOutput);
|
|
if (FAILED(hr))
|
|
{
|
|
break;
|
|
}
|
|
else if (bIsOutput)
|
|
{
|
|
// This is an output pin.
|
|
bFoundOuputPin = true;
|
|
}
|
|
else
|
|
{
|
|
// It's an input pin. Is it mapped to an output pin?
|
|
ULONG nPin = 0;
|
|
hr = pPin->QueryInternalConnections(NULL, &nPin);
|
|
if (hr == S_OK)
|
|
{
|
|
// The count (nPin) was zero, and the method returned S_OK, so
|
|
// this input pin is mapped to exactly zero ouput pins.
|
|
// Therefore, it is a rendered input pin.
|
|
bFoundRenderedInputPin = true;
|
|
|
|
// We have met condition (2) above, so we can stop looking.
|
|
break;
|
|
|
|
// Note: S_FALSE here means "the count (nPin) was not large
|
|
// enough", ie, this pin is mapped to one or more output pins.
|
|
|
|
}
|
|
else if (hr == E_NOTIMPL)
|
|
{
|
|
// This pin is not mapped to any particular output pin.
|
|
bFoundUnmappedInputPin = true;
|
|
hr = S_OK;
|
|
}
|
|
else if (FAILED(hr))
|
|
{
|
|
// Unexpected error
|
|
break;
|
|
}
|
|
|
|
}
|
|
pPin->Release(); // Release for the next loop.
|
|
} // while
|
|
|
|
|
|
if (bFoundRenderedInputPin)
|
|
{
|
|
bIsRenderer = TRUE; // condition (1) above
|
|
}
|
|
else if (bFoundUnmappedInputPin && !bFoundOuputPin)
|
|
{
|
|
bIsRenderer = TRUE; // condition (2) above
|
|
}
|
|
else
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*pResult = bIsRenderer;
|
|
}
|
|
|
|
SAFE_RELEASE(pFlags);
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pPin);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: SupportsPropertyPage
|
|
// Desc: Query whether a filter has a property page.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline BOOL FilterSupportsPropertyPage(IBaseFilter *pFilter)
|
|
{
|
|
if (pFilter == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ISpecifyPropertyPages *pProp = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages,
|
|
(void**)&pProp);
|
|
|
|
SAFE_RELEASE(pProp);
|
|
return (SUCCEEDED(hr));
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: ShowFilterPropertyPage
|
|
// Desc: Show a filter's property page.
|
|
//
|
|
// hwndParent: Owner window for the property page.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT ShowFilterPropertyPage(IBaseFilter *pFilter, HWND hwndParent)
|
|
{
|
|
HRESULT hr;
|
|
|
|
ISpecifyPropertyPages *pSpecify = NULL;
|
|
FILTER_INFO FilterInfo;
|
|
CAUUID caGUID;
|
|
|
|
if (!pFilter)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
ZeroMemory(&FilterInfo, sizeof(FilterInfo));
|
|
ZeroMemory(&caGUID, sizeof(caGUID));
|
|
|
|
// Discover if this filter contains a property page
|
|
CHECK_HR(hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpecify));
|
|
|
|
CHECK_HR(hr = pFilter->QueryFilterInfo(&FilterInfo));
|
|
|
|
CHECK_HR(hr = pSpecify->GetPages(&caGUID));
|
|
|
|
// Display the filter's property page
|
|
CHECK_HR(hr = OleCreatePropertyFrame(
|
|
hwndParent, // Parent window
|
|
0, // x (Reserved)
|
|
0, // y (Reserved)
|
|
FilterInfo.achName, // Caption for the dialog box
|
|
1, // Number of filters
|
|
(IUnknown **)&pFilter, // Pointer to the filter
|
|
caGUID.cElems, // Number of property pages
|
|
caGUID.pElems, // Pointer to property page CLSIDs
|
|
0, // Locale identifier
|
|
0, // Reserved
|
|
NULL // Reserved
|
|
));
|
|
|
|
done:
|
|
CoTaskMemFree(caGUID.pElems);
|
|
SAFE_RELEASE(FilterInfo.pGraph);
|
|
SAFE_RELEASE(pSpecify);
|
|
return hr;
|
|
}
|
|
|
|
/**************************************************************************
|
|
|
|
Graph Building Helper Functions
|
|
|
|
**************************************************************************/
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: DisconnectPin
|
|
// Desc: Disconnect a pin from its peer.
|
|
//
|
|
// Note: If the pin is not connected, the function returns S_OK (no-op).
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT DisconnectPin(IGraphBuilder *pGraph, IPin *pPin)
|
|
{
|
|
if (!pGraph || !pPin)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IPin *pPinTo = NULL;
|
|
|
|
hr = pPin->ConnectedTo(&pPinTo);
|
|
if (hr == VFW_E_NOT_CONNECTED)
|
|
{
|
|
// This pin is not connected.
|
|
return S_OK; // no-op
|
|
}
|
|
|
|
// Disconnect the first pin.
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pGraph->Disconnect(pPin);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Disconnect the other pin.
|
|
hr = pGraph->Disconnect(pPinTo);
|
|
}
|
|
|
|
SAFE_RELEASE(pPinTo);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: RemoveFilter
|
|
// Desc: Removes a filter from the graph.
|
|
//
|
|
// Note: The function first disconnects the filter's pins. If the method
|
|
// fails, some pins may be disconnected, so the graph is in an
|
|
// unknown state.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT RemoveFilter(IGraphBuilder *pGraph, IBaseFilter *pFilter)
|
|
{
|
|
if (!pGraph || !pFilter)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IEnumPins *pEnum = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
CHECK_HR(hr = pFilter->EnumPins(&pEnum));
|
|
|
|
// Disconnect all the pins
|
|
while (S_OK == pEnum->Next(1, &pPin, 0))
|
|
{
|
|
CHECK_HR(hr = DisconnectPin(pGraph, pPin));
|
|
SAFE_RELEASE(pPin);
|
|
}
|
|
|
|
CHECK_HR(hr = pGraph->RemoveFilter(pFilter));
|
|
|
|
done:
|
|
SAFE_RELEASE(pPin);
|
|
SAFE_RELEASE(pEnum);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: AddFilterByCLSID
|
|
// Desc: Create a filter by CLSID and add it to the graph.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT AddFilterByCLSID(
|
|
IGraphBuilder *pGraph, // Pointer to the Filter Graph Manager.
|
|
const GUID& clsid, // CLSID of the filter to create.
|
|
IBaseFilter **ppF, // Receives a pointer to the filter.
|
|
LPCWSTR wszName = NULL // A name for the filter (can be NULL).
|
|
)
|
|
{
|
|
if (!pGraph || ! ppF)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
*ppF = 0;
|
|
|
|
IBaseFilter *pFilter = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
CHECK_HR(hr = CoCreateInstance(
|
|
clsid,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IBaseFilter,
|
|
(void**)&pFilter));
|
|
|
|
CHECK_HR(hr = pGraph->AddFilter(pFilter, wszName));
|
|
|
|
*ppF = pFilter;
|
|
(*ppF)->AddRef();
|
|
|
|
done:
|
|
SAFE_RELEASE(pFilter);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: AddFilterFromMoniker
|
|
// Desc: Create a filter from an IMoniker pointer and add it to the graph.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT AddFilterFromMoniker(
|
|
IGraphBuilder *pGraph, // Pointer to the Filter Graph Manager.
|
|
IMoniker *pFilterMoniker, // Pointer to the moniker.
|
|
IBaseFilter **ppF, // Receives a pointer to the filter.
|
|
LPCWSTR wszName // A name for the filter (can be NULL).
|
|
)
|
|
{
|
|
if (!pGraph || !pFilterMoniker || !ppF)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IBaseFilter *pFilter = NULL;
|
|
|
|
// Use the moniker to create the filter
|
|
CHECK_HR(hr = pFilterMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pFilter));
|
|
|
|
// Add the capture filter to the filter graph
|
|
CHECK_HR(hr = pGraph->AddFilter(pFilter, wszName));
|
|
|
|
// Return to the caller.
|
|
*ppF = pFilter;
|
|
(*ppF)->AddRef();
|
|
|
|
done:
|
|
SAFE_RELEASE(pFilter);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: AddSourceFilter
|
|
// Desc: Loads a source filter.
|
|
//
|
|
// pGraph: Pointer to the filter graph manager.
|
|
// szFile: File name.
|
|
// clsid: CLSID of the source filter to use.
|
|
// ppSourceFilter: Receives a pointer to the source filter's IBaseFilter
|
|
// interface.
|
|
//
|
|
// This function creates the filter, adds it to the graph, queries for
|
|
// IFileSourceFilter, and calls IFileSourceFilter::Load.
|
|
//
|
|
// You can use this function if you want to specify the source filter by
|
|
// CLSID. Otherwise, just use IGraphBuilder::AddSourceFilter.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT AddSourceFilter(
|
|
IGraphBuilder *pGraph, // Pointer to the filter graph manager.
|
|
const WCHAR *szFile, // File name
|
|
const GUID& clsidSourceFilter, // CLSID of the source filter to use
|
|
IBaseFilter **ppSourceFilter) // receives a pointer to the filter.
|
|
{
|
|
if (!pGraph || !szFile || !ppSourceFilter)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IBaseFilter *pSource = NULL;
|
|
IFileSourceFilter *pFileSource = NULL;
|
|
|
|
// Create the source filter and add it to the graph.
|
|
CHECK_HR(hr = CoCreateInstance(
|
|
clsidSourceFilter,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IBaseFilter,
|
|
(void**)&pSource));
|
|
|
|
|
|
CHECK_HR(hr = pGraph->AddFilter(pSource, szFile));
|
|
CHECK_HR(hr = pSource->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSource));
|
|
CHECK_HR(hr = pFileSource->Load(szFile, NULL));
|
|
|
|
// Return the filter pointer to the caller.
|
|
*ppSourceFilter = pSource;
|
|
(*ppSourceFilter)->AddRef();
|
|
|
|
done:
|
|
if (FAILED(hr))
|
|
{
|
|
// FAILED, remove the filter
|
|
if (pSource != NULL)
|
|
{
|
|
RemoveFilter(pGraph, pSource);
|
|
}
|
|
}
|
|
|
|
SAFE_RELEASE(pSource);
|
|
SAFE_RELEASE(pFileSource);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: AddWriterFilter
|
|
// Desc: Adds a file-writing filter. (ie, a filter that supports
|
|
// IFileSinkFilter.)
|
|
//
|
|
// pGraph: Pointer to the filter graph manager.
|
|
// szFile: File name.
|
|
// clsid: CLSID of the filter.
|
|
// bOverwrite: If TRUE, overwrite the file.
|
|
// ppSourceFilter: Receives the filter's IBaseFilter interface.
|
|
//
|
|
// This function creates the filter, adds it to the graph, sets the
|
|
// file name, and set the overwrite mode.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT AddWriterFilter(
|
|
IGraphBuilder *pGraph,
|
|
const WCHAR *szFile,
|
|
const GUID& clsid,
|
|
BOOL bOverwrite,
|
|
IBaseFilter **ppFilter
|
|
)
|
|
{
|
|
if (pGraph == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if (szFile == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if (ppFilter == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IBaseFilter *pFilter = NULL;
|
|
IFileSinkFilter *pSink = NULL;
|
|
IFileSinkFilter2 *pSink2 = NULL;
|
|
|
|
CHECK_HR(hr = AddFilterByCLSID(pGraph, clsid, &pFilter, szFile));
|
|
|
|
CHECK_HR(hr = pFilter->QueryInterface(IID_IFileSinkFilter, (void**)&pSink));
|
|
CHECK_HR(hr = pSink->SetFileName((LPCOLESTR)szFile, NULL));
|
|
|
|
if (bOverwrite)
|
|
{
|
|
// Try to use IFileSinkFilter2 to set the file-overwrite mode, but if the filter
|
|
// does not implement IFileSinkFilter2, just ignore it.
|
|
if (SUCCEEDED(pFilter->QueryInterface(IID_IFileSinkFilter2, (void**)&pSink2)))
|
|
{
|
|
CHECK_HR(hr = pSink2->SetMode(AM_FILE_OVERWRITE));
|
|
}
|
|
}
|
|
|
|
*ppFilter = pFilter;
|
|
(*ppFilter)->AddRef();
|
|
|
|
done:
|
|
if (FAILED(hr))
|
|
{
|
|
RemoveFilter(pGraph, pFilter);
|
|
}
|
|
|
|
SAFE_RELEASE(pFilter);
|
|
SAFE_RELEASE(pSink);
|
|
SAFE_RELEASE(pSink2);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: ConnectFilters
|
|
// Desc: Connect two filters.
|
|
//
|
|
// There are 3 overrides on this method:
|
|
// (1) From output pin to filter
|
|
// (2) From filter to filter
|
|
// (3) From filter to input pin
|
|
//
|
|
// Note: The 4th combination (output pin to input pin) is already
|
|
// provided by the IGraphBuilder::Connect method.
|
|
//
|
|
// All of these methods search the filter(s) for the first unconnected
|
|
// pin. If that pin fails, the method fails. In some cases, it may be
|
|
// better to try every pin until one succeeds, or loop through the pins
|
|
// and examine the preferred media types.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
// ConnectFilters: Filter to pin
|
|
|
|
inline HRESULT ConnectFilters(
|
|
IGraphBuilder *pGraph, // Filter Graph Manager.
|
|
IPin *pOut, // Output pin on the upstream filter.
|
|
IBaseFilter *pDest) // Downstream filter.
|
|
{
|
|
if ((pGraph == NULL) || (pOut == NULL) || (pDest == NULL))
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
IPin *pIn = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Find an input pin on the downstream filter.
|
|
CHECK_HR(hr = FindUnconnectedPin(pDest, PINDIR_INPUT, &pIn));
|
|
|
|
// Try to connect them.
|
|
CHECK_HR(hr = pGraph->Connect(pOut, pIn));
|
|
|
|
done:
|
|
SAFE_RELEASE(pIn);
|
|
return hr;
|
|
}
|
|
|
|
// ConnectFilters: Filter to filter
|
|
|
|
inline HRESULT ConnectFilters(
|
|
IGraphBuilder *pGraph,
|
|
IBaseFilter *pSrc,
|
|
IBaseFilter *pDest)
|
|
{
|
|
if ((pGraph == NULL) || (pSrc == NULL) || (pDest == NULL))
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IPin *pOut = NULL;
|
|
|
|
// Find an output pin on the first filter.
|
|
CHECK_HR(hr = FindUnconnectedPin(pSrc, PINDIR_OUTPUT, &pOut));
|
|
CHECK_HR(hr = ConnectFilters(pGraph, pOut, pDest));
|
|
|
|
done:
|
|
SAFE_RELEASE(pOut);
|
|
return hr;
|
|
}
|
|
|
|
// ConnectFilters: Filter to pin
|
|
|
|
inline HRESULT ConnectFilters(IGraphBuilder *pGraph, IBaseFilter *pSrc, IPin *pIn)
|
|
{
|
|
if ((pGraph == NULL) || (pSrc == NULL) || (pIn == NULL))
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IPin *pOut = NULL;
|
|
|
|
// Find an output pin on the upstream filter.
|
|
CHECK_HR(hr = FindUnconnectedPin(pSrc, PINDIR_OUTPUT, &pOut));
|
|
|
|
// Try to connect them.
|
|
CHECK_HR(hr = pGraph->Connect(pOut, pIn));
|
|
|
|
done:
|
|
SAFE_RELEASE(pOut);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: CreateKernelFilter
|
|
// Desc: Create a kernel-mode filter by name.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT CreateKernelFilter(
|
|
const GUID &guidCategory, // Filter category.
|
|
LPCOLESTR szName, // The name of the filter.
|
|
IBaseFilter **ppFilter // Receives a pointer to the filter.
|
|
)
|
|
{
|
|
if (!szName || !ppFilter)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
bool bFound = false;
|
|
|
|
ICreateDevEnum *pDevEnum = NULL;
|
|
IEnumMoniker *pEnum = NULL;
|
|
IMoniker *pMoniker = NULL;
|
|
|
|
// Create the system device enumerator.
|
|
CHECK_HR(hr = CoCreateInstance(CLSID_SystemDeviceEnum,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_ICreateDevEnum,
|
|
(void**)&pDevEnum));
|
|
|
|
// Create a class enumerator for the specified category.
|
|
CHECK_HR(hr = pDevEnum->CreateClassEnumerator(guidCategory, &pEnum, 0));
|
|
if (hr != S_OK) // S_FALSE means the category is empty.
|
|
{
|
|
CHECK_HR(hr = E_FAIL);
|
|
}
|
|
|
|
// Enumerate devices within this category.
|
|
while (!bFound && (S_OK == pEnum->Next(1, &pMoniker, 0)))
|
|
{
|
|
// Get the property bag
|
|
IPropertyBag *pBag = NULL;
|
|
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
|
|
if (FAILED(hr))
|
|
{
|
|
SAFE_RELEASE(pMoniker);
|
|
continue; // Maybe the next one will work.
|
|
}
|
|
|
|
// Check the friendly name.
|
|
VARIANT var;
|
|
VariantInit(&var);
|
|
hr = pBag->Read(L"FriendlyName", &var, NULL);
|
|
if (SUCCEEDED(hr) && (lstrcmpiW(var.bstrVal, szName) == 0))
|
|
{
|
|
// This is the right filter.
|
|
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter,
|
|
(void**)ppFilter);
|
|
bFound = true;
|
|
}
|
|
VariantClear(&var);
|
|
SAFE_RELEASE(pBag);
|
|
SAFE_RELEASE(pMoniker);
|
|
|
|
} // while
|
|
|
|
if (!bFound)
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pDevEnum);
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pMoniker);
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindFilterInterface
|
|
// Desc: Search the graph for a filter that exposes a specified interface.
|
|
//
|
|
// pGraph Pointer to the Filter Graph Manager.
|
|
// iid IID of the interface to retrieve.
|
|
// ppUnk Receives the interface pointer.
|
|
// Q Address of an ATL smart pointer.
|
|
//
|
|
// Note: This function returns the first instance that it finds.
|
|
// If no filter is found, the function returns VFW_E_NOT_FOUND.
|
|
// The templated version takes an ATL smart pointer and deduces the IID.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindFilterInterface(
|
|
IGraphBuilder *pGraph, // Pointer to the Filter Graph Manager.
|
|
REFGUID iid, // IID of the interface to retrieve.
|
|
void **ppUnk) // Receives the interface pointer.
|
|
{
|
|
if (!pGraph || !ppUnk)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
bool bFound = false;
|
|
|
|
IEnumFilters *pEnum = NULL;
|
|
IBaseFilter *pFilter = NULL;
|
|
|
|
CHECK_HR(hr = pGraph->EnumFilters(&pEnum));
|
|
|
|
// Query every filter for the interface.
|
|
while (S_OK == pEnum->Next(1, &pFilter, 0))
|
|
{
|
|
hr = pFilter->QueryInterface(iid, ppUnk);
|
|
SAFE_RELEASE(pFilter);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pFilter);
|
|
|
|
return (bFound ? S_OK : VFW_E_NOT_FOUND);
|
|
}
|
|
|
|
template <class Q>
|
|
HRESULT FindFilterInterface(IGraphBuilder *pGraph, Q** pp)
|
|
{
|
|
return FindFilterInterface(pGraph, __uuidof(Q), (void**)pp);
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: FindGraphInterface
|
|
// Desc: Search the graph for a filter OR pin that exposes a specified
|
|
// interface.
|
|
//
|
|
// pGraph Pointer to the Filter Graph Manager.
|
|
// iid IID of the interface.
|
|
// ppUnk Receives the interface pointer.
|
|
// Q Address of an ATL smart pointer.
|
|
//
|
|
// Note: This function returns the first instance that it finds.
|
|
// If no match is found, the function returns VFW_E_NOT_FOUND.
|
|
// The templated version takes an ATL smart pointer and deduces the IID.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT FindGraphInterface(
|
|
IGraphBuilder *pGraph,
|
|
REFGUID iid,
|
|
void **ppUnk)
|
|
{
|
|
if (!pGraph || !ppUnk)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
bool bFound = false;
|
|
|
|
IEnumFilters *pEnum = NULL;
|
|
IBaseFilter *pFilter = NULL;
|
|
|
|
CHECK_HR(hr = pGraph->EnumFilters(&pEnum));
|
|
|
|
// Loop through every filter in the graph.
|
|
while (S_OK == pEnum->Next(1, &pFilter, 0))
|
|
{
|
|
hr = pFilter->QueryInterface(iid, ppUnk);
|
|
if (FAILED(hr))
|
|
{
|
|
// The filter does not expose the interface, but maybe
|
|
// one of its pins does.
|
|
hr = FindPinInterface(pFilter, iid, ppUnk);
|
|
}
|
|
|
|
SAFE_RELEASE(pFilter);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pFilter);
|
|
|
|
return (bFound ? S_OK : VFW_E_NOT_FOUND);
|
|
}
|
|
|
|
template <class Q>
|
|
HRESULT FindGraphInterface(IGraphBuilder *pGraph, Q** pp)
|
|
{
|
|
return FindGraphInterface(pGraph, __uuidof(Q), (void**)pp);
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: GetConnectedFilter
|
|
// Desc: Returns the filter on the other side of a pin.
|
|
//
|
|
// ie, Given a pin, get the pin connected to it, and return that pin's filter.
|
|
//
|
|
// pPin Pointer to the pin.
|
|
// ppFilter Receives a pointer to the filter.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT GetConnectedFilter(IPin *pPin, IBaseFilter **ppFilter)
|
|
{
|
|
if (!pPin || !ppFilter)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IPin *pPeer = NULL;
|
|
PIN_INFO info;
|
|
|
|
ZeroMemory(&info, sizeof(info));
|
|
|
|
CHECK_HR(hr = pPin->ConnectedTo(&pPeer));
|
|
CHECK_HR(hr = pPeer->QueryPinInfo(&info));
|
|
|
|
assert(info.pFilter != NULL);
|
|
if (info.pFilter)
|
|
{
|
|
*ppFilter = info.pFilter; // Return pointer to caller.
|
|
(*ppFilter)->AddRef();
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = E_UNEXPECTED; // Pin does not have an owning filter! That's weird!
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pPeer);
|
|
SAFE_RELEASE(info.pFilter);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: GetNextFilter
|
|
// Desc: Find a filter's upstream or downstream neighbor.
|
|
// (Returns the first instance found.)
|
|
//
|
|
// If there is no matching filter, the function returns VFW_E_NOT_CONNECTED.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT inline GetNextFilter(
|
|
IBaseFilter *pFilter, // Pointer to the starting filter
|
|
GraphDirection Dir, // Direction to search (upstream or downstream)
|
|
IBaseFilter **ppNext) // Receives a pointer to the next filter.
|
|
{
|
|
|
|
PIN_DIRECTION PinDirection = (Dir == UPSTREAM ? PINDIR_INPUT : PINDIR_OUTPUT);
|
|
|
|
if (!pFilter || !ppNext)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
IPin *pPin = NULL;
|
|
HRESULT hr = FindConnectedPin(pFilter, PinDirection, &pPin);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = GetConnectedFilter(pPin, ppNext);
|
|
}
|
|
|
|
SAFE_RELEASE(pPin);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Name: RemoveFiltersDownstreamOfPin
|
|
//
|
|
// Removes any filters downstream from pPin, AND removes the filter that owns pPin.
|
|
// (This is a recursive function used by RemoveFiltersDownstream, see below.)
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT RemoveFiltersDownstreamOfPin(IGraphBuilder *pGraph, IPin *pPin)
|
|
{
|
|
// If the pin is an output pin, and the pin is connected, then
|
|
// 1. Get the owning filter
|
|
// 2. Call RemoveFiltersDownstream recursively
|
|
// 3. Disconnect the pin
|
|
// 4. Remove the owning filter
|
|
|
|
BOOL bIsOutput = FALSE;
|
|
HRESULT hr = S_OK;
|
|
IBaseFilter *pFilter = NULL;
|
|
|
|
CHECK_HR(hr = IsPinDirection(pPin, PINDIR_OUTPUT, &bIsOutput));
|
|
|
|
if (!bIsOutput)
|
|
{
|
|
// This pin is not an input pin, so there is nothing to do.
|
|
return S_OK;
|
|
}
|
|
|
|
// Get the owning filter.
|
|
hr = GetConnectedFilter(pPin, &pFilter);
|
|
if (hr == VFW_E_NOT_CONNECTED)
|
|
{
|
|
hr = S_OK; // This pin was not connected, so there is nothing to do.
|
|
}
|
|
else if (SUCCEEDED(hr))
|
|
{
|
|
// Call recursively to remove the filters downstream from this pin.
|
|
CHECK_HR(hr = RemoveFiltersDownstream(pGraph, pFilter));
|
|
|
|
// Now remove the owning filter.
|
|
CHECK_HR(hr = RemoveFilter(pGraph, pFilter));
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pFilter);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: RemoveFiltersDownstream
|
|
// Desc: Remove a filter from the graph, including all filters
|
|
// downstream from that filter.
|
|
//
|
|
// Note: This function removes pFilter from the graph and
|
|
// removes every filter that is downstream from pFilter.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT RemoveFiltersDownstream(IGraphBuilder *pGraph, IBaseFilter *pFilter)
|
|
{
|
|
if (!pGraph || !pFilter)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IEnumPins *pEnum = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
CHECK_HR(hr = pFilter->EnumPins(&pEnum));
|
|
|
|
while (S_OK == pEnum->Next(1, &pPin, 0))
|
|
{
|
|
CHECK_HR(hr = RemoveFiltersDownstreamOfPin(pGraph, pPin));
|
|
SAFE_RELEASE(pPin);
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pPin);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Name: RemoveUnconnectedFilters
|
|
// Desc: Remove all unconnected filters from the graph.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT RemoveUnconnectedFilters(IGraphBuilder *pGraph)
|
|
{
|
|
if (pGraph == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
IEnumFilters *pEnum = NULL;
|
|
IBaseFilter *pFilter = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
CHECK_HR(hr = pGraph->EnumFilters(&pEnum));
|
|
|
|
// Go through the list of filters in the graph.
|
|
while (S_OK == pEnum->Next(1, &pFilter, NULL))
|
|
{
|
|
// Find a connected pin on this filter.
|
|
HRESULT hr2 = FindMatchingPin(pFilter, MatchPinConnection(TRUE), &pPin);
|
|
if (SUCCEEDED(hr2))
|
|
{
|
|
// If it's connected, don't remove the filter.
|
|
SAFE_RELEASE(pPin);
|
|
continue;
|
|
}
|
|
assert(pPin == NULL);
|
|
CHECK_HR(hr = RemoveFilter(pGraph, pFilter));
|
|
|
|
// The previous call made the enumerator stale.
|
|
pEnum->Reset();
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pFilter);
|
|
SAFE_RELEASE(pPin);
|
|
return hr;
|
|
}
|
|
|
|
/**************************************************************************
|
|
|
|
Misc. Helper Functions
|
|
|
|
**************************************************************************/
|
|
|
|
// CopyFormatBlock:
|
|
// Allocates memory for the format block in the media type and copies the
|
|
// buffer into the format block. Also releases the previous format block.
|
|
inline HRESULT CopyFormatBlock(AM_MEDIA_TYPE *pmt, const BYTE *pFormat, DWORD cbSize)
|
|
{
|
|
if (pmt == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (cbSize == 0)
|
|
{
|
|
_FreeMediaType(*pmt);
|
|
return S_OK;
|
|
}
|
|
|
|
if (pFormat == NULL)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Reallocate the format block.
|
|
// Note: It is valid to pass NULL to CoTaskMemRealloc.
|
|
// Note: If CoTaskMemRealloc fails to allocate a new block, it does
|
|
// free the old block.
|
|
BYTE *pbFormat = (BYTE*)CoTaskMemRealloc(pmt->pbFormat, cbSize);
|
|
if (pbFormat == NULL)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
pmt->pbFormat = pbFormat;
|
|
pmt->cbFormat = cbSize;
|
|
CopyMemory(pmt->pbFormat, pFormat, cbSize);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
// RectWidth: Returns the width of a rectangle.
|
|
inline LONG RectWidth(const RECT& rc) { return rc.right - rc.left; }
|
|
|
|
// RectHeight: Returns the height of a rectangle.
|
|
inline LONG RectHeight(const RECT& rc) { return rc.bottom - rc.top; }
|
|
|
|
|
|
/********************* Time conversion functions *********************/
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// FramesPerSecToFrameLength
|
|
// Converts from frames-to-second to frame duration.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline REFERENCE_TIME FramesPerSecToFrameLength(double fps)
|
|
{
|
|
return (REFERENCE_TIME)((double)ONE_SECOND / fps);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// RefTimeToMsec
|
|
// Convert REFERENCE_TIME units to milliseconds (taken from CRefTime)
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline LONG RefTimeToMsec(const REFERENCE_TIME& time)
|
|
{
|
|
return (LONG)(time / (ONE_SECOND / ONE_MSEC));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// RefTimeToSeconds
|
|
// Converts reference time (100 nanosecond units) to floating-point seconds.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline double RefTimeToSeconds(const REFERENCE_TIME& rt)
|
|
{
|
|
return double(rt) / double(ONE_SECOND);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// SecondsToRefTime
|
|
// Converts seconds to reference time.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline REFERENCE_TIME SecondsToRefTime(const double& sec)
|
|
{
|
|
return (REFERENCE_TIME)(sec * double(ONE_SECOND));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// MsecToRefTime
|
|
// Converts milliseconds to reference time.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline REFERENCE_TIME MsecToRefTime(const LONG& msec)
|
|
{
|
|
return (REFERENCE_TIME)(msec * ONE_MSEC);
|
|
}
|
|
|
|
|
|
/********************* GraphEdit functions *********************/
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: SaveGraphFile
|
|
// Desc: Save a filter graph to a .grf (GraphEdit) file.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT SaveGraphFile(IGraphBuilder *pGraph, WCHAR *wszPath)
|
|
{
|
|
if (pGraph == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
const WCHAR wszStreamName[] = L"ActiveMovieGraph";
|
|
|
|
HRESULT hr = S_OK;
|
|
IStorage *pStorage = NULL;
|
|
IStream *pStream = NULL;
|
|
IPersistStream *pPersist = NULL;
|
|
|
|
CHECK_HR(hr = StgCreateDocfile(
|
|
wszPath,
|
|
STGM_CREATE | STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
|
|
0, &pStorage));
|
|
|
|
CHECK_HR(hr = pStorage->CreateStream(
|
|
wszStreamName,
|
|
STGM_WRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
|
|
0,
|
|
0,
|
|
&pStream));
|
|
CHECK_HR(hr = pGraph->QueryInterface(IID_IPersistStream, (void**)&pPersist));
|
|
|
|
CHECK_HR(hr = pPersist->Save(pStream, TRUE));
|
|
|
|
CHECK_HR(hr = pStorage->Commit(STGC_DEFAULT));
|
|
|
|
done:
|
|
SAFE_RELEASE(pStorage);
|
|
SAFE_RELEASE(pStream);
|
|
SAFE_RELEASE(pPersist);
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: LoadGraphFile
|
|
// Desc: Load a .grf (GraphEdit) file.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT LoadGraphFile(IGraphBuilder *pGraph, const WCHAR* wszName)
|
|
{
|
|
if (pGraph == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (wszName == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (S_OK != StgIsStorageFile(wszName))
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
IStorage *pStorage = NULL;
|
|
IPersistStream *pPersistStream = NULL;
|
|
IStream *pStream = NULL;
|
|
|
|
CHECK_HR(hr = StgOpenStorage(
|
|
wszName,
|
|
0,
|
|
STGM_TRANSACTED | STGM_READ | STGM_SHARE_DENY_WRITE,
|
|
0,
|
|
0,
|
|
&pStorage));
|
|
|
|
CHECK_HR(hr = pGraph->QueryInterface(IID_IPersistStream, (void**)&pPersistStream));
|
|
|
|
CHECK_HR(hr = pStorage->OpenStream(L"ActiveMovieGraph", 0,
|
|
STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream));
|
|
|
|
CHECK_HR(hr = pPersistStream->Load(pStream));
|
|
|
|
done:
|
|
SAFE_RELEASE(pStorage);
|
|
SAFE_RELEASE(pPersistStream);
|
|
SAFE_RELEASE(pStream);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: AddGraphToRot
|
|
// Desc: Adds a DirectShow filter graph to the Running Object Table,
|
|
// allowing GraphEdit to load a remote filter graph if enabled.
|
|
//
|
|
// pUnkGraph: Pointer to the Filter Graph Manager
|
|
// pdwRegister: Receives a token. Pass this value to RemoveFromRot.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
// Adds a DirectShow filter graph to the Running Object Table,
|
|
// allowing GraphEdit to load a remote filter graph.
|
|
inline HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
|
|
{
|
|
IMoniker * pMoniker = NULL;
|
|
IRunningObjectTable *pROT = NULL;
|
|
WCHAR wsz[128];
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!pUnkGraph || !pdwRegister)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
CHECK_HR(hr = GetRunningObjectTable(0, &pROT));
|
|
|
|
// Format the string for the registration
|
|
CHECK_HR(hr = StringCchPrintfW(wsz, NUMELMS(wsz), L"FilterGraph %08x pid %08x\0",
|
|
(DWORD_PTR)pUnkGraph, GetCurrentProcessId()));
|
|
|
|
// Create the moniker
|
|
CHECK_HR(hr = CreateItemMoniker(L"!", wsz, &pMoniker));
|
|
|
|
// 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.
|
|
CHECK_HR(hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,
|
|
pMoniker, pdwRegister));
|
|
|
|
done:
|
|
SAFE_RELEASE(pMoniker);
|
|
SAFE_RELEASE(pROT);
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: RemoveGraphFromRot
|
|
// Desc: Removes a DirectShow filter graph from the Running Object Table.
|
|
//
|
|
// dwRegister: Token returned by AddGraphToRot function.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline void RemoveGraphFromRot(DWORD dwRegister)
|
|
{
|
|
IRunningObjectTable *pROT = NULL;
|
|
|
|
if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
|
|
{
|
|
pROT->Revoke(dwRegister);
|
|
}
|
|
|
|
SAFE_RELEASE(pROT);
|
|
}
|
|
|
|
|
|
/********************* Audio and Video Format Functions *********************/
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: CreatePCMAudioType
|
|
// Desc: Initialize a PCM audio type with a WAVEFORMATEX format block.
|
|
// (This function does not handle WAVEFORMATEXTENSIBLE formats.)
|
|
//
|
|
// If the method succeeds, call FreeMediaType to free the format block.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT CreatePCMAudioType(
|
|
AM_MEDIA_TYPE& mt, // Media type to populate
|
|
WORD nChannels, // Number of channels
|
|
DWORD nSamplesPerSec, // Samples per second
|
|
WORD wBitsPerSample // Bits per sample
|
|
)
|
|
{
|
|
_FreeMediaType(mt);
|
|
|
|
|
|
mt.pbFormat = (BYTE*)CoTaskMemAlloc(sizeof(WAVEFORMATEX));
|
|
if (!mt.pbFormat)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
mt.cbFormat = sizeof(WAVEFORMATEX);
|
|
|
|
mt.majortype = MEDIATYPE_Audio;
|
|
mt.subtype = MEDIASUBTYPE_PCM;
|
|
mt.formattype = FORMAT_WaveFormatEx;
|
|
|
|
WAVEFORMATEX *pWav = (WAVEFORMATEX*)mt.pbFormat;
|
|
pWav->wFormatTag = WAVE_FORMAT_PCM;
|
|
pWav->nChannels = nChannels;
|
|
pWav->nSamplesPerSec = nSamplesPerSec;
|
|
pWav->wBitsPerSample = wBitsPerSample;
|
|
pWav->cbSize = 0;
|
|
|
|
// Derived values
|
|
pWav->nBlockAlign = nChannels * (wBitsPerSample / 8);
|
|
pWav->nAvgBytesPerSec = nSamplesPerSec * pWav->nBlockAlign;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: CreateRGBVideoType
|
|
// Desc: Initialize an uncompressed RGB video media type.
|
|
// (Allocates the palette table for palettized video)
|
|
//
|
|
// mt: Media type to populate
|
|
// iBitDepth: Bits per pixel. Must be 1, 4, 8, 16, 24, or 32
|
|
// Width: Width in pixels
|
|
// Height: Height in pixels. Use > 0 for bottom-up DIBs, < 0 for top-down DIB
|
|
// fps: Frame rate, in frames per second
|
|
//
|
|
// If the method succeeds, call FreeMediaType to free the format block.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT CreateRGBVideoType(AM_MEDIA_TYPE &mt, WORD iBitDepth, long Width, long Height,
|
|
double fps)
|
|
{
|
|
DWORD color_mask_565[] = { 0x00F800, 0x0007E0, 0x00001F };
|
|
|
|
|
|
if ((iBitDepth != 1) && (iBitDepth != 4) && (iBitDepth != 8) &&
|
|
(iBitDepth != 16) && (iBitDepth != 24) && (iBitDepth != 32))
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (Width < 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
_FreeMediaType(mt);
|
|
|
|
mt.pbFormat = (BYTE*)CoTaskMemAlloc(sizeof(VIDEOINFO));
|
|
if (!mt.pbFormat)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
mt.cbFormat = sizeof(VIDEOINFO);
|
|
|
|
|
|
VIDEOINFO *pvi = (VIDEOINFO*)mt.pbFormat;
|
|
ZeroMemory(pvi, sizeof(VIDEOINFO));
|
|
|
|
pvi->AvgTimePerFrame = FramesPerSecToFrameLength(fps);
|
|
|
|
BITMAPINFOHEADER *pBmi = &(pvi->bmiHeader);
|
|
pBmi->biSize = sizeof(BITMAPINFOHEADER);
|
|
pBmi->biWidth = Width;
|
|
pBmi->biHeight = Height;
|
|
pBmi->biPlanes = 1;
|
|
pBmi->biBitCount = iBitDepth;
|
|
|
|
if (iBitDepth == 16)
|
|
{
|
|
pBmi->biCompression = BI_BITFIELDS;
|
|
memcpy(pvi->dwBitMasks, color_mask_565, sizeof(DWORD) * 3);
|
|
}
|
|
else
|
|
{
|
|
pBmi->biCompression = BI_RGB;
|
|
}
|
|
|
|
if (iBitDepth <= 8)
|
|
{
|
|
// Palettized format.
|
|
pBmi->biClrUsed = PALETTE_ENTRIES(pvi);
|
|
|
|
HDC hdc = GetDC(NULL); // hdc for the current display.
|
|
if (hdc == NULL)
|
|
{
|
|
_FreeMediaType(mt);
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
GetSystemPaletteEntries(hdc, 0, pBmi->biClrUsed, (PALETTEENTRY*)pvi->bmiColors);
|
|
|
|
ReleaseDC(NULL, hdc);
|
|
}
|
|
|
|
pvi->bmiHeader.biSizeImage = DIBSIZE(pvi->bmiHeader);
|
|
|
|
mt.majortype == MEDIATYPE_Video;
|
|
mt.subtype = FORMAT_VideoInfo;
|
|
|
|
switch (iBitDepth)
|
|
{
|
|
case 1:
|
|
mt.subtype = MEDIASUBTYPE_RGB1;
|
|
break;
|
|
case 4:
|
|
mt.subtype = MEDIASUBTYPE_RGB4;
|
|
break;
|
|
case 8:
|
|
mt.subtype = MEDIASUBTYPE_RGB8;
|
|
break;
|
|
case 16:
|
|
mt.subtype = MEDIASUBTYPE_RGB565;
|
|
break;
|
|
case 24:
|
|
mt.subtype = MEDIASUBTYPE_RGB24;
|
|
break;
|
|
case 32:
|
|
mt.subtype = MEDIASUBTYPE_RGB32;
|
|
}
|
|
|
|
mt.lSampleSize = pvi->bmiHeader.biSizeImage;
|
|
mt.bTemporalCompression = FALSE;
|
|
mt.bFixedSizeSamples = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Name: LetterBoxRect
|
|
// Desc: Find the largest rectangle that fits inside rcDest and has
|
|
// the specified aspect ratio.
|
|
//
|
|
// aspectRatio: Desired aspect ratio
|
|
// rcDest: Destination rectangle (defines the bounds)
|
|
// prcResult: Pointer to a RECT struct. The method fills in the
|
|
// struct with the letterboxed rectangle.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
inline HRESULT LetterBoxRect(const SIZE &aspectRatio, const RECT &rcDest, RECT *prcResult)
|
|
{
|
|
if (prcResult == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
// Avoid divide by zero (even though MulDiv handles this)
|
|
if (aspectRatio.cx == 0 || aspectRatio.cy == 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
LONG width, height;
|
|
|
|
LONG SrcWidth = aspectRatio.cx;
|
|
LONG SrcHeight = aspectRatio.cy;
|
|
LONG DestWidth = rcDest.right - rcDest.left;
|
|
LONG DestHeight = rcDest.bottom - rcDest.top;
|
|
|
|
|
|
// First try: Letterbox along the sides. ("pillarbox")
|
|
width = MulDiv(DestHeight, SrcWidth, SrcHeight);
|
|
height = DestHeight;
|
|
if (width > DestWidth)
|
|
{
|
|
// Letterbox along the top and bottom.
|
|
width = DestWidth;
|
|
height = MulDiv(DestWidth, SrcHeight, SrcWidth);
|
|
}
|
|
|
|
if (width == -1 || height == -1)
|
|
{
|
|
// MulDiv caught an overflow or divide by zero)
|
|
return E_FAIL;
|
|
}
|
|
|
|
assert(width <= DestWidth);
|
|
assert(height <= DestHeight);
|
|
|
|
// Fill in the rectangle
|
|
prcResult->left = rcDest.left + ((DestWidth - width) / 2);
|
|
prcResult->right = prcResult->left + width;
|
|
prcResult->top = rcDest.top + ((DestHeight - height) / 2);
|
|
prcResult->bottom = prcResult->top + height;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/********************* Media type functions *********************/
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// SetMediaTypeFormat: Sets the format block of a media type
|
|
//
|
|
// pmt: Pointer to the AM_MEDIA_TYPE structure. Cannot be NULL
|
|
// pBuffer: Pointer to the format block. (Can be NULL)
|
|
// cbBuffer: Size of pBuffer in bytes
|
|
//
|
|
// This function clears the old format block and copies the new
|
|
// format block into the media type structure.
|
|
//----------------------------------------------------------------------------
|
|
|
|
inline HRESULT SetMediaTypeFormatBlock(AM_MEDIA_TYPE *pmt, BYTE *pBuffer, DWORD cbBuffer)
|
|
{
|
|
if (!pmt)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if (!pBuffer && cbBuffer > 0)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
pmt->pbFormat = (BYTE*)CoTaskMemRealloc(pmt->pbFormat, cbBuffer);
|
|
pmt->cbFormat = cbBuffer;
|
|
|
|
if (cbBuffer > 0)
|
|
{
|
|
if (pmt->pbFormat)
|
|
{
|
|
CopyMemory(pmt->pbFormat, pBuffer, cbBuffer);
|
|
}
|
|
else
|
|
{
|
|
pmt->cbFormat = 0;
|
|
hr = E_OUTOFMEMORY; // CoTaskMemRealloc failed
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
// The following functions are defined in the DirectShow base class library.
|
|
// They are redefined here for convenience, because many applications do not
|
|
// need to link to the base class library.
|
|
|
|
// FreeMediaType: Release the format block for a media type.
|
|
inline void _FreeMediaType(AM_MEDIA_TYPE& mt)
|
|
{
|
|
if (mt.cbFormat != 0)
|
|
{
|
|
CoTaskMemFree((PVOID)mt.pbFormat);
|
|
mt.cbFormat = 0;
|
|
mt.pbFormat = NULL;
|
|
}
|
|
if (mt.pUnk != NULL)
|
|
{
|
|
// Unecessary because pUnk should not be used, but safest.
|
|
mt.pUnk->Release();
|
|
mt.pUnk = NULL;
|
|
}
|
|
}
|
|
|
|
// DeleteMediaType:
|
|
// Delete a media type structure that was created on the heap, including the
|
|
// format block)
|
|
inline void _DeleteMediaType(AM_MEDIA_TYPE *pmt)
|
|
{
|
|
if (pmt != NULL)
|
|
{
|
|
_FreeMediaType(*pmt);
|
|
CoTaskMemFree(pmt);
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef __STREAMS__
|
|
#define FreeMediaType _FreeMediaType
|
|
#define DeleteMediaType _DeleteMediaType
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef __IVMRWindowlessControl9_FWD_DEFINED__
|
|
|
|
// AddVMR9Filter
|
|
inline HRESULT AddVMR9Filter(
|
|
IGraphBuilder *pGraph, // Pointer to the Filter Graph Manager
|
|
HWND hwndVideo, // Handle to the application's video window.
|
|
DWORD cMaxStreams, // Maximum number of video streams.
|
|
IVMRWindowlessControl9 **ppWindowless // Optional.
|
|
)
|
|
{
|
|
if (pGraph == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if (hwndVideo == NULL)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
if (cMaxStreams < 1)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
// ppWindowless can be NULL
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
IBaseFilter *pVMR = NULL;
|
|
IVMRFilterConfig9 *pConfig = NULL;;
|
|
IVMRWindowlessControl9 *pWindowless = NULL;
|
|
|
|
CHECK_HR(hr = AddFilterByCLSID(pGraph, CLSID_VideoMixingRenderer9, &pVMR, L"Video Mixing Renderer 9"));
|
|
|
|
CHECK_HR(hr = pVMR->QueryInterface(IID_IVMRFilterConfig9, (LPVOID *)&pConfig));
|
|
|
|
CHECK_HR(hr = pConfig->SetNumberOfStreams(cMaxStreams));
|
|
|
|
CHECK_HR(hr = pConfig->SetRenderingMode(VMR9Mode_Windowless));
|
|
|
|
CHECK_HR(hr = pVMR->QueryInterface(IID_IVMRWindowlessControl9, (LPVOID *)&pWindowless));
|
|
|
|
CHECK_HR(hr = pWindowless->SetVideoClippingWindow(hwndVideo));
|
|
|
|
CHECK_HR(hr = pWindowless->SetAspectRatioMode(VMR9ARMode_LetterBox));
|
|
|
|
if (ppWindowless)
|
|
{
|
|
*ppWindowless = pWindowless;
|
|
(*ppWindowless)->AddRef();
|
|
}
|
|
|
|
done:
|
|
SAFE_RELEASE(pVMR);
|
|
SAFE_RELEASE(pConfig);
|
|
SAFE_RELEASE(pWindowless);
|
|
return hr;
|
|
}
|
|
|
|
#endif // __IVMRWindowlessControl9_FWD_DEFINED__
|
|
|
|
|
|
// RenderFileToVideoRenderer:
|
|
// Renders a media file to an existing video renderer in the graph.
|
|
// NOTE: The caller must add the video renderer to the graph before calling this method.
|
|
inline HRESULT RenderFileToVideoRenderer(IGraphBuilder *pGraph, WCHAR *wFileName, BOOL bRenderAudio = TRUE)
|
|
{
|
|
if (pGraph == NULL || wFileName == NULL)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
BOOL bRenderedAnyPin = FALSE;
|
|
|
|
IBaseFilter *pSource = NULL;
|
|
IPin *pPin = NULL;
|
|
IFilterGraph2 *pGraph2 = NULL;
|
|
IEnumPins *pEnum = NULL;
|
|
IBaseFilter *pAudioRenderer = NULL;
|
|
|
|
// Get the IFilterGraph2 interface from the Filter Graph Manager.
|
|
CHECK_HR(hr = pGraph->QueryInterface(IID_IFilterGraph2, (void**)&pGraph2));
|
|
|
|
// Add the source filter.
|
|
CHECK_HR(hr = pGraph->AddSourceFilter(wFileName, L"SOURCE", &pSource));
|
|
|
|
// Add the DSound Renderer to the graph.
|
|
if (bRenderAudio)
|
|
{
|
|
CHECK_HR(hr = AddFilterByCLSID(pGraph, CLSID_DSoundRender, &pAudioRenderer, L"Audio Renderer"));
|
|
}
|
|
|
|
// Enumerate the pins on the source filter.
|
|
CHECK_HR(hr = pSource->EnumPins(&pEnum));
|
|
|
|
// Loop through all the pins
|
|
|
|
while (S_OK == pEnum->Next(1, &pPin, NULL))
|
|
{
|
|
// Try to render this pin.
|
|
// It's OK if we fail some pins, if at least one pin renders.
|
|
HRESULT hr2 = pGraph2->RenderEx(pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
|
|
|
|
pPin->Release();
|
|
|
|
if (SUCCEEDED(hr2))
|
|
{
|
|
bRenderedAnyPin = TRUE;
|
|
}
|
|
}
|
|
|
|
// Try to remove any unconnected filters.
|
|
CHECK_HR(hr = RemoveUnconnectedFilters(pGraph));
|
|
|
|
done:
|
|
SAFE_RELEASE(pEnum);
|
|
SAFE_RELEASE(pAudioRenderer);
|
|
SAFE_RELEASE(pGraph2);
|
|
|
|
// If we succeeded to this point, make sure we rendered at least one
|
|
// stream.
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (!bRenderedAnyPin)
|
|
{
|
|
hr = VFW_E_CANNOT_RENDER;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|