////////////////////////////////////////////////////////////////////////// // 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 #include #include #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 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 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 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 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; }