1323 lines
55 KiB
C++
1323 lines
55 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// File: CDVGraph.cpp
|
|
//
|
|
// Desc: CDVGraph Class definition, it supports DV Graph Building
|
|
// This is the class to build all AVC graphs using
|
|
// MSTape.sys
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "CDVGraph.h"
|
|
|
|
#include "dbgsup.h"
|
|
|
|
BOOL IsDeviceOutputDV(IBaseFilter * pFilter);
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
| Function: CDVGraph::CDVGraph
|
|
| Purpose: Constructor for digital tv graph class.
|
|
| Arguments: None
|
|
| Returns: None
|
|
| Notes: initializes the digital tv graph components
|
|
\----------------------------------------------------------------------------*/
|
|
CDVGraph::CDVGraph(void)
|
|
: m_VideoFormat( DVENCODERVIDEOFORMAT_NTSC )
|
|
, m_DVResolution( DVRESOLUTION_HALF )
|
|
, m_SubunitMode ( VcrMode )
|
|
, m_pGraph( NULL )
|
|
, m_pCaptureGraphBuilder( NULL )
|
|
, m_pMediaControl( NULL )
|
|
, m_pMediaEvent( NULL )
|
|
, m_pInputFileFilter (NULL )
|
|
, m_pVideoWindow( NULL )
|
|
, m_pDeviceFilter( NULL )
|
|
, m_pIAMExtDevice( NULL )
|
|
, m_pIAMExtTransport( NULL )
|
|
, m_pIAMTCReader( NULL )
|
|
, m_pDroppedFrames( NULL )
|
|
{
|
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
| Function: CDVGraph::~CDVGraph
|
|
| Purpose: Destructor for the graph info class.
|
|
| Arguments: None
|
|
| Returns: None
|
|
| Notes: Any clean up needed should be put here.
|
|
\----------------------------------------------------------------------------*/
|
|
CDVGraph::~CDVGraph(void)
|
|
{
|
|
if(m_pVideoWindow != NULL)
|
|
{
|
|
//Otherwise, a video image remains on the screen and the user cannot get rid of it.
|
|
m_pVideoWindow->put_Visible(OAFALSE);
|
|
|
|
//Otherwise, messages are sent to the wrong window, likely causing errors.
|
|
m_pVideoWindow->put_Owner(NULL);
|
|
}
|
|
|
|
FreeFilters();
|
|
CoUninitialize();
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
| Function: CDVGraph::FreeFilters
|
|
| Purpose: Destructor for the graph info class.
|
|
| Arguments: None
|
|
| Returns: None
|
|
| Notes: Any clean up needed should be put here.
|
|
\----------------------------------------------------------------------------*/
|
|
void CDVGraph::FreeFilters()
|
|
{
|
|
// Release the DirectShow interfaces created
|
|
SAFE_RELEASE(m_pGraph)
|
|
SAFE_RELEASE(m_pCaptureGraphBuilder)
|
|
SAFE_RELEASE(m_pMediaEvent);
|
|
SAFE_RELEASE(m_pMediaControl)
|
|
SAFE_RELEASE(m_pInputFileFilter);
|
|
SAFE_RELEASE(m_pDeviceFilter);
|
|
SAFE_RELEASE(m_pIAMExtDevice);
|
|
SAFE_RELEASE(m_pIAMExtTransport);
|
|
SAFE_RELEASE(m_pIAMTCReader);
|
|
SAFE_RELEASE(m_pVideoWindow);
|
|
SAFE_RELEASE(m_pDroppedFrames);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
| Function: CAVCGraph::GraphInitialize
|
|
| Purpose: Initializing the graph class members.
|
|
| Arguments: None
|
|
| Returns: Boolean TRUE if successfull or throws exception detailing the error
|
|
| Notes: Initializes the required DirectShow interfaces
|
|
\----------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::InitializeGraph()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
Dump1( TEXT(" CDVGraph::InitializeGraph().hr = %x\0"), hr );
|
|
|
|
// All DirectShow FilterGraphs need this
|
|
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
|
|
IID_IGraphBuilder, (LPVOID *)&m_pGraph );
|
|
CHECK_ERROR( TEXT(" Failed to create FilterGraph."), hr);
|
|
|
|
// Helps the building all Graphs
|
|
hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC,
|
|
(REFIID)IID_ICaptureGraphBuilder2,
|
|
(void **)&m_pCaptureGraphBuilder);
|
|
CHECK_ERROR( TEXT(" Failed to create CaptureGraphBuilder2."), hr);
|
|
|
|
hr = m_pCaptureGraphBuilder->SetFiltergraph(m_pGraph);
|
|
CHECK_ERROR( TEXT(" Failed to SetFiltergraph."), hr);
|
|
|
|
hr = m_pGraph->QueryInterface(IID_IMediaEventEx,
|
|
reinterpret_cast<PVOID *>(&m_pMediaEvent));
|
|
CHECK_ERROR( TEXT(" Failed to QI IMediaEventEx."), hr);
|
|
|
|
// DirectShow Interface for Run, Stop, Pause the flow of the streams through the filter graph
|
|
hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **) &m_pMediaControl);
|
|
CHECK_ERROR( TEXT(" Failed to QI IMediaControl."), hr);
|
|
|
|
hr = m_pGraph->QueryInterface(IID_IVideoWindow, (void **) &m_pVideoWindow);
|
|
CHECK_ERROR( TEXT(" Failed to QI IVideoWindow."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
| Function: CDVGraph::BuildBasicGraph
|
|
| Purpose: Add device filter to the graph
|
|
| Arguments: None
|
|
| Returns: HRESULT
|
|
| Notes:
|
|
\----------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::BuildBasicGraph()
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = InitializeGraph();
|
|
CHECK_ERROR( TEXT(" Failed to initialize graph."), hr);
|
|
|
|
hr = AddDeviceFilter();
|
|
CHECK_ERROR( TEXT(" Failed to add device filter."), hr);
|
|
|
|
ASSERT(m_pDeviceFilter);
|
|
|
|
hr = m_pDeviceFilter->QueryInterface(IID_IAMExtTransport,
|
|
(void **) &m_pIAMExtTransport);
|
|
CHECK_ERROR( TEXT(" Failed to QI IAMExtTransport."), hr);
|
|
|
|
hr = m_pDeviceFilter->QueryInterface(IID_IAMExtDevice, (void **) &m_pIAMExtDevice);
|
|
CHECK_ERROR( TEXT(" Failed to QI IAMExtDevice."), hr);
|
|
|
|
hr = m_pDeviceFilter->QueryInterface(IID_IAMTimecodeReader, (void **) &m_pIAMTCReader);
|
|
CHECK_ERROR( TEXT(" Failed to QI IAMTimecodeReader."), hr);
|
|
|
|
hr= GetDVMode( &m_SubunitMode );
|
|
CHECK_ERROR( TEXT(" GetMode Failed."), hr);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
| Function: CDVGraph::AddDeviceFilter
|
|
| Purpose: Load (not add) a filter of the name "Microsoft DV Camera and VCR"
|
|
| which connects to the specified filter
|
|
| Arguments: None
|
|
| Returns: HRESULT
|
|
| Notes: This method adds the device filter to the filtergraph
|
|
\----------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::AddDeviceFilter()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
ICreateDevEnum * pCreateDevEnum = NULL;
|
|
IEnumMoniker * pEnumMoniker = NULL;
|
|
IMoniker * pMoniker = NULL;
|
|
ULONG nFetched = 0;
|
|
|
|
// Create Device Enumerator
|
|
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_ICreateDevEnum, reinterpret_cast<PVOID *>(&pCreateDevEnum));
|
|
CHECK_ERROR( TEXT(" Failed to create SystemDeviceEnum."), hr);
|
|
|
|
// Create the enumerator of the monikers for the specified Device Class & reset them
|
|
hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumMoniker, 0);
|
|
if(SUCCEEDED(hr) && pEnumMoniker)
|
|
pEnumMoniker->Reset();
|
|
else {
|
|
Dump( TEXT(" Failed to CreateClassEnumerator.") );
|
|
return hr;
|
|
}
|
|
|
|
|
|
// Loop through to the last moniker
|
|
while(SUCCEEDED(pEnumMoniker->Next( 1, &pMoniker, &nFetched )) && pMoniker)
|
|
{
|
|
|
|
// get the device friendly name:
|
|
IPropertyBag *pPropBag;
|
|
hr = pMoniker->BindToStorage( 0, 0, IID_IPropertyBag, (void **)&pPropBag );
|
|
CHECK_ERROR( TEXT(" Failed to BindToStorage."), hr);
|
|
|
|
//Friendly name
|
|
VARIANT varFriendlyName;
|
|
varFriendlyName.vt = VT_BSTR;
|
|
hr = pPropBag->Read( L"FriendlyName", &varFriendlyName, 0 );
|
|
CHECK_ERROR( TEXT(" Failed to read friendlyname."), hr);
|
|
|
|
#ifdef UNICODE
|
|
(void)StringCchCopy( m_DeviceName, NUMELMS(m_DeviceName), varFriendlyName.bstrVal );
|
|
#else
|
|
WideCharToMultiByte( CP_ACP, 0, varFriendlyName.bstrVal, -1, m_DeviceName, sizeof(m_DeviceName), 0, 0 );
|
|
#endif
|
|
|
|
VariantClear( &varFriendlyName );
|
|
|
|
|
|
// detect DV device by search media type of its output pins for DV type
|
|
IBaseFilter *pDeviceFilter;
|
|
|
|
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pDeviceFilter );
|
|
CHECK_ERROR(TEXT("CAVCGraph::AddDeviceFilter():: BindToObject failed"), hr);
|
|
if(pDeviceFilter== NULL)
|
|
return E_FAIL;
|
|
|
|
if(IsDeviceOutputDV(pDeviceFilter))
|
|
{
|
|
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pDeviceFilter );
|
|
CHECK_ERROR( TEXT(" Failed to BindToObject."), hr);
|
|
|
|
hr = m_pGraph->AddFilter(m_pDeviceFilter, L"Filter");
|
|
CHECK_ERROR( TEXT(" Failed to AddFilter."), hr);
|
|
|
|
SAFE_RELEASE(pMoniker);
|
|
SAFE_RELEASE(pDeviceFilter);
|
|
break;
|
|
}
|
|
else
|
|
hr = E_FAIL;
|
|
|
|
SAFE_RELEASE(pMoniker);
|
|
SAFE_RELEASE(pDeviceFilter);
|
|
}//end of while
|
|
|
|
SAFE_RELEASE( pEnumMoniker );
|
|
SAFE_RELEASE( pCreateDevEnum );
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: CDVGraph::GetTapeInfo
|
|
Purpose: Get Frame rate and availability of dvcr tape
|
|
Arguments: None
|
|
Returns: HRESULT as appropriate
|
|
Notes:
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::GetTapeInfo(void)
|
|
{
|
|
HRESULT hr;
|
|
LONG lMediaType = 0;
|
|
LONG lInSignalMode = 0;
|
|
|
|
// Query Media Type of the transport
|
|
hr = m_pIAMExtTransport->GetStatus(ED_MEDIA_TYPE, &lMediaType);
|
|
CHECK_ERROR( TEXT(" GetStatus failed."), hr);
|
|
|
|
if (ED_MEDIA_NOT_PRESENT == lMediaType)
|
|
{
|
|
// Return failure if there is no tape installed
|
|
hr = S_FALSE;
|
|
}
|
|
else
|
|
{
|
|
// Tape type should always be DVC
|
|
ASSERT(ED_MEDIA_DVC == lMediaType);
|
|
|
|
// Now lets query for the signal mode of the tape.
|
|
hr = m_pIAMExtTransport->GetTransportBasicParameters(ED_TRANSBASIC_INPUT_SIGNAL,
|
|
&lInSignalMode, NULL);
|
|
CHECK_ERROR( TEXT(" GetTransportBasicParameters failed."), hr);
|
|
|
|
// determine whether the camcorder supports ntsc or pal
|
|
switch (lInSignalMode)
|
|
{
|
|
case ED_TRANSBASIC_SIGNAL_525_60_SD :
|
|
m_AvgTimePerFrame = 33; // 33 milliseconds (29.97 FPS)
|
|
m_VideoFormat = DVENCODERVIDEOFORMAT_NTSC;
|
|
break;
|
|
|
|
case ED_TRANSBASIC_SIGNAL_525_60_SDL :
|
|
m_AvgTimePerFrame = 33; // 33 milliseconds (29.97 FPS)
|
|
m_VideoFormat = DVENCODERVIDEOFORMAT_NTSC;
|
|
break;
|
|
|
|
case ED_TRANSBASIC_SIGNAL_625_50_SD :
|
|
m_AvgTimePerFrame = 40; // 40 milliseconds (25 FPS)
|
|
m_VideoFormat = DVENCODERVIDEOFORMAT_PAL;
|
|
break;
|
|
|
|
case ED_TRANSBASIC_SIGNAL_625_50_SDL :
|
|
m_AvgTimePerFrame = 40; // 40 milliseconds (25 FPS)
|
|
m_VideoFormat = DVENCODERVIDEOFORMAT_PAL;
|
|
break;
|
|
|
|
default :
|
|
Dump(TEXT("Unsupported or unrecognized tape format type."));
|
|
m_AvgTimePerFrame = 33; // 33 milli-sec (29.97 FPS); default
|
|
break;
|
|
}
|
|
Dump1(TEXT("Avg time per frame is %d FPS\0"), m_AvgTimePerFrame);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: CDVGraph::DV_GetDVMode
|
|
Purpose: Determines camera mode using IAMExtDevice::GetCapability()
|
|
Arguments: None
|
|
Returns: Subunit mode of camera device
|
|
Notes:
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::GetDVMode(DV_MODE *pSubunitMode)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LONG lDeviceType = 0;
|
|
|
|
ASSERT(m_pDeviceFilter);
|
|
ASSERT(pSubunitMode);
|
|
|
|
if (!pSubunitMode)
|
|
return E_POINTER;
|
|
if (!m_pIAMExtDevice)
|
|
return E_NOINTERFACE;
|
|
|
|
// Query the Device Type Capability
|
|
hr = m_pIAMExtDevice->GetCapability(ED_DEVCAP_DEVICE_TYPE, &lDeviceType, 0);
|
|
CHECK_ERROR( TEXT(" m_pIAMExtDevice->GetCapability() failed."), hr);
|
|
|
|
switch (lDeviceType)
|
|
{
|
|
case 0 :
|
|
//device type is unknown
|
|
*pSubunitMode = UnknownMode;
|
|
break;
|
|
|
|
case ED_DEVTYPE_VCR :
|
|
*pSubunitMode = VcrMode;
|
|
break;
|
|
|
|
case ED_DEVTYPE_CAMERA :
|
|
*pSubunitMode = CameraMode;
|
|
break;
|
|
|
|
default :
|
|
Dump(TEXT("GetCapability returned an unknown device type!"));
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: DV_SaveGraph
|
|
Purpose: Save the filter graph into a *.grf file
|
|
Arguments: FileName
|
|
Returns: HRESULT as appropriate
|
|
Notes:
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::SaveGraphToFile(TCHAR* sGraphFile)
|
|
{
|
|
IStorage * pStorage = NULL;
|
|
IStream * pStream = NULL;
|
|
IPersistStream * pPersistStream = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
if(m_pCaptureGraphBuilder == NULL || sGraphFile == NULL)
|
|
return E_FAIL;
|
|
|
|
// Either Open or Create the *.GRF file
|
|
hr = StgOpenStorage( sGraphFile, NULL, STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_DENY_WRITE,
|
|
NULL, NULL, &pStorage );
|
|
if ( STG_E_FILENOTFOUND == hr )
|
|
hr = StgCreateDocfile( sGraphFile, STGM_CREATE | STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE ,
|
|
NULL , &pStorage);
|
|
CHECK_ERROR( TEXT(" StgCreateDocfile failed."), hr);
|
|
|
|
hr = pStorage->CreateStream( L"ActiveMovieGraph", STGM_WRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
|
|
NULL, NULL, &pStream );
|
|
CHECK_ERROR( TEXT(" CreateStream failed."), hr);
|
|
|
|
// Persist the stream, save & commit to disk
|
|
hr = m_pGraph->QueryInterface( IID_IPersistStream, (void **) &pPersistStream );
|
|
CHECK_ERROR( TEXT(" QI IPersistStream failed."), hr);
|
|
|
|
hr = pPersistStream->Save(pStream, TRUE);
|
|
CHECK_ERROR( TEXT(" pPersistStream->Save() failed."), hr);
|
|
|
|
hr = pStorage->Commit( STGC_DEFAULT );
|
|
CHECK_ERROR( TEXT(" Save GRF file failed."), hr);
|
|
|
|
SAFE_RELEASE(pStorage);
|
|
SAFE_RELEASE(pStream);
|
|
SAFE_RELEASE(pPersistStream);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: CDVGraph::MakePreviewGraph()
|
|
Purpose: Builds the DV preview graph
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a preview graph for DV :
|
|
DV_Cam(AV Out)->DVSplitter(vid)->DVCodec->VideoWindow
|
|
DVSplitter(aud)->Default DirectSound device
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakePreviewGraph()
|
|
{
|
|
m_iGraphType = GRAPH_PREVIEW;
|
|
|
|
HRESULT hr = m_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
|
|
&MEDIATYPE_Interleaved,
|
|
m_pDeviceFilter,
|
|
NULL, NULL);
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: CDVGraph::MakeDvToFileGraph_Type1
|
|
Purpose: Builds and runs the DV to File graph
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a capture & preview graph for DV Type 1 AVI files
|
|
This graph is a bit more complex. It looks like this:
|
|
DV_Cam(AV Out)->SmartTee(capture)->AviMux->FileWriter
|
|
SmartTee(preview)->DVSplitter(vid)->DVCodec->VideoWindow
|
|
DVSplitter(aud)->Default DirectSound device
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeDvToFileGraph_Type1(TCHAR* OutputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_DV_TO_FILE;
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(OutputFileName[0]);
|
|
|
|
SmartPtr<IBaseFilter> ppf;
|
|
SmartPtr<IFileSinkFilter> pSink;
|
|
hr = m_pCaptureGraphBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, OutputFileName, &ppf, &pSink);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_Type1::m_pCaptureGraphBuilder->SetOutputFileName() failed."), hr);
|
|
|
|
hr = SetAviOptions(ppf, INTERLEAVE_NONE);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_Type1::m_pCaptureGraphBuilder->SetAviOptions() failed."), hr);
|
|
|
|
// The graph we're making is: MSDV --> Smart Tee --> AVI Mux --> File Writer
|
|
// --> DV Splitter --> DV Decoder --> Video Renderer
|
|
// --> Audio Renderer
|
|
// Connect interleaved stream of MSDV to the AVI Mux/FW
|
|
hr = m_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, m_pDeviceFilter, NULL, ppf);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
// Build a preview graph off of it too
|
|
hr = m_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, m_pDeviceFilter, NULL, NULL);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: DV_MakeDvToFileGraph_NoPre
|
|
Purpose: Builds and runs the DV to File graph with no preview
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a capture only graph for DV Type 1 AVI files
|
|
This graph is not too complex. It looks like this:
|
|
DV_Cam(AV Out)->AviMux->FileWriter
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeDvToFileGraph_NoPre_Type1(TCHAR* OutputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_DV_TO_FILE_NOPRE;
|
|
HRESULT hr = S_OK;
|
|
ASSERT(OutputFileName[0]) ;
|
|
|
|
//add the avimux, and file writer to the graph
|
|
SmartPtr<IBaseFilter> ppf = NULL;
|
|
SmartPtr<IFileSinkFilter> pSink = NULL;
|
|
|
|
hr = m_pCaptureGraphBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, OutputFileName, &ppf, &pSink);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pCaptureGraphBuilder->SetOutputFileName() failed."), hr);
|
|
|
|
hr = SetAviOptions(ppf, INTERLEAVE_NONE);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
// the graph we're making is: MSDV --> AVI Mux --> File Writer
|
|
// Set the AVI Options like interleaving mode etc...
|
|
// Build the graph
|
|
hr = m_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, m_pDeviceFilter,
|
|
NULL, ppf);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: DV_MakeFileToDvGraph
|
|
Purpose: Builds and runs the File to DV graph
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a transmit & playback graph for DV Type 1 AVI files
|
|
This graph is a bit more complex. It looks like this:
|
|
FileSource->AVI_Splitter->InfPinTee->DV_Camera
|
|
InfPinTee->DVSplitter(vid)->DVDecoder->VideoWIndow
|
|
DVSplitter(aud)->Default DirectSound device
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeFileToDvGraph_Type1(TCHAR* InputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_FILE_TO_DV;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Add the file as source filter to the graph
|
|
hr = m_pGraph->AddSourceFilter(InputFileName, InputFileName, &m_pInputFileFilter);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeFileToDvGraph_Type1::m_pGraph->AddSourceFilter failed."), hr);
|
|
|
|
SmartPtr<IBaseFilter> pAviSplitter = NULL;
|
|
SmartPtr<IBaseFilter> pInfTee = NULL;
|
|
|
|
hr = CoCreateInstance(CLSID_AviSplitter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<PVOID *>(&pAviSplitter));
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::CoCreate AviSplitter failed."), hr);
|
|
|
|
hr = CoCreateInstance(CLSID_InfTee, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<PVOID *>(&pInfTee));
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::CoCreate InfTee failed."), hr);
|
|
|
|
// the graph we're making is: Async Reader --> AVI Splitter --> Tee --> MSDV
|
|
// --> DV Splitter --> DV Decoder --> VidRend
|
|
// --> AudRend
|
|
// Add the AVI Splitter
|
|
hr = m_pGraph->AddFilter(pAviSplitter, L"AVI Splitter");
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pGraph->AddFilter failed."), hr);
|
|
|
|
// Connect file source to the splitter
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, m_pInputFileFilter, NULL, pAviSplitter);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// Add an infinite Tee
|
|
hr = m_pGraph->AddFilter(pInfTee, L"Infinite Tee");
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pGraph->AddFilter() failed."), hr);
|
|
|
|
// Connect the Splitter to the Tee
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, &MEDIATYPE_Interleaved, pAviSplitter, NULL, pInfTee);
|
|
CHECK_ERROR( TEXT(" CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
// Connect one branch of the tee to MSDV
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, pInfTee, NULL, m_pDeviceFilter);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
// Build a preview graph off the other branch
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, pInfTee, NULL, NULL);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: DV_MakeFileToDvGraph_NoPre
|
|
Purpose: Builds and runs the File to DV graph without preview
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a transmit only graph for DV Type 1 AVI files
|
|
This graph is a bit simplex. It looks like this:
|
|
FileSource->AVI_Splitter->DV_Camera
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeFileToDvGraph_NoPre_Type1(TCHAR* InputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_FILE_TO_DV_NOPRE;
|
|
HRESULT hr = S_OK;
|
|
ASSERT(InputFileName[0]) ;
|
|
|
|
// Add the file as source filter to the graph
|
|
hr = m_pGraph->AddSourceFilter(InputFileName, InputFileName, &m_pInputFileFilter);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDVGraph_NoPre_Type1::m_pGraph->AddSourceFilter() failed."), hr);
|
|
|
|
// the graph we're making is: Async Reader --> AVI Splitter --> MSDV
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, m_pInputFileFilter, NULL, m_pDeviceFilter);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDVGraph_NoPre_Type1::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: DV_MakeDvToFileGraph_Type2
|
|
Purpose: Builds and runs the DV to File graph
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a capture & preview graph for DV Type 2 AVI files
|
|
This graph is a bit more complex. It looks like this:
|
|
DV_Cam(AV Out)->DVSplitter(vid)->SmartTee(capture)->AviMux->FileWriter
|
|
SmartTee(preview)->DVCodec->VideoWindow
|
|
DVSplitter(aud)->InfinitePinTee->AviMux->FileWriter
|
|
InfinitePinTee->Default DirectSound device
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeDvToFileGraph_Type2(TCHAR* OutputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_DV_TO_FILE_TYPE2;
|
|
HRESULT hr = S_OK;
|
|
|
|
// making sure there is a output file selected
|
|
ASSERT(OutputFileName[0]);
|
|
|
|
SmartPtr<IBaseFilter> pDVSplitter = NULL;
|
|
hr = CoCreateInstance(CLSID_DVSplitter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<PVOID *>(&pDVSplitter));
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDVToFileGraph_Type2::create DVSplitter failed."), hr);
|
|
|
|
hr = m_pGraph->AddFilter(pDVSplitter, L"DV Splitter");
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDVToFileGraph_Type2::m_pGraph->AddFilter() failed."), hr);
|
|
|
|
|
|
//add the avimux, and file writer to the graph
|
|
SmartPtr<IBaseFilter> ppf = NULL;
|
|
SmartPtr<IFileSinkFilter> pSink = NULL;
|
|
hr = m_pCaptureGraphBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, OutputFileName, &ppf, &pSink);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDVToFileGraph_Type2::m_pCaptureGraphBuilder->SetOutputFileName() failed."), hr);
|
|
|
|
// Set the AVI Options like interleaving mode etc...
|
|
hr = SetAviOptions(ppf, INTERLEAVE_NONE);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDVToFileGraph_Type2::SetAviOptions() failed."), hr);
|
|
|
|
// the graph we're making is: MSDV --> Smart Tee --> DV SPLITTER --> AVI MUX --> File Writer
|
|
// --> DV SPLITTER --> DV DEC --> Video Renderer
|
|
// --> Audio Renderer
|
|
|
|
// Connect MSDV Interleave stream through DV Splitter to AVI MUX/FW
|
|
hr = m_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, m_pDeviceFilter, pDVSplitter, ppf);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDVToFileGraph_Type2::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
// Connect the other DV Splitter output to the AVI MUX/FW
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, pDVSplitter, NULL, ppf);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDVToFileGraph_Type2::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
// Render the preview part of the graph
|
|
hr = m_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, m_pDeviceFilter, NULL, NULL);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDVToFileGraph_Type2::m_pCaptureGraphBuilder->RenderStream() failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: DV_MakeDvToFileGraph_NoPre_Type2
|
|
Purpose: Builds and runs the DV to File graph
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a capture only graph for DV Type 2 AVI files
|
|
This graph is a bit simplex . It looks like this:
|
|
DV_Cam(AV Out)->DVSplitter(vid)->AviMux->FileWriter
|
|
DVSplitter(aud)->AviMux->FileWriter
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeDvToFileGraph_NoPre_Type2(TCHAR* OutputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_DV_TO_FILE_NOPRE_TYPE2;
|
|
HRESULT hr = S_OK;
|
|
|
|
// making sure there is a output file selected
|
|
ASSERT(OutputFileName[0]);
|
|
|
|
SmartPtr<IBaseFilter> pDVSplitter = NULL;
|
|
|
|
hr = CoCreateInstance(CLSID_DVSplitter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<PVOID *>(&pDVSplitter));
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type2::create DVSplitter failed."), hr);
|
|
|
|
hr = m_pGraph->AddFilter(pDVSplitter, L"DV Splitter");
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type2::m_pGraph->AddFilter failed."), hr);
|
|
|
|
//add the avimux, and file writer to the graph
|
|
SmartPtr<IBaseFilter> ppf = NULL;
|
|
SmartPtr<IFileSinkFilter> pSink = NULL;
|
|
|
|
hr = m_pCaptureGraphBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, OutputFileName, &ppf, &pSink);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type2::m_pCaptureGraphBuilder->SetOutputFileName failed."), hr);
|
|
|
|
// the graph we're making is: MSDV --> Smart Tee --> DV SPLITTER --> AVI MUX --> File Writer
|
|
//
|
|
// connect MSDV interleaved pin through DV Splitter to AVI MUX/FW
|
|
hr = m_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, m_pDeviceFilter, pDVSplitter, ppf);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// connect the other DV Splitter output to the AVI MUX/FW
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, pDVSplitter, NULL, ppf);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// Set the AVI Options like interleaving mode etc...
|
|
hr = SetAviOptions(ppf, INTERLEAVE_NONE);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeDvToFileGraph_NoPre_Type2::SetAviOptions failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: DV_MakeFileToDvGraph_Type2
|
|
Purpose: Builds and runs the File to DV graph
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a transmit & playback graph for DV Type 2 AVI files
|
|
This graph is a bit complex. It looks like this:
|
|
FileSource->AVI_Splitter(vid) ->DVMuxer(vid)---------->InfPinTee->DV_Camera
|
|
AVI_Splitter(aud)->DVMuxer(aud) InfPinTee->DVSplitter(vid)->DVDecoder->VideoWIndow
|
|
DVSplitter(aud)->DSoundDevice
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeFileToDvGraph_Type2(TCHAR* InputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_FILE_TO_DV_TYPE2;
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(InputFileName[0] );
|
|
|
|
SmartPtr<IBaseFilter> pDVMux = NULL;
|
|
SmartPtr<IBaseFilter> pInfTee = NULL;
|
|
hr = CoCreateInstance(CLSID_DVMux, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<PVOID *>(&pDVMux));
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::create DVMux failed."), hr);
|
|
|
|
hr = CoCreateInstance(CLSID_InfTee, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<PVOID *>(&pInfTee));
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::CoCreate InfTeefailed."), hr);
|
|
|
|
// Add the file as source filter to the graph
|
|
hr =m_pGraph->AddSourceFilter(InputFileName, InputFileName, &m_pInputFileFilter);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pGraph->AddSourceFilter failed."), hr);
|
|
|
|
// Add the DVMuxer to the graph
|
|
hr = m_pGraph->AddFilter(pDVMux, L"DV Muxer");
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pGraph->AddFilter failed."), hr);
|
|
|
|
//Add the infinite pin tee filter to the graph and connect it downstream to the dv muxer
|
|
hr = m_pGraph->AddFilter(pInfTee, L"Infinite Tee");
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pGraph->AddFilter failed."), hr);
|
|
|
|
// the graph we need to build is: ASYNC reader --> AVI SPLITTER --> DV MUX --> TEE --> MSDV
|
|
// --> DVSP --> DV DEC --> VR
|
|
// --> AR
|
|
// connect file source video stream to DV MUX
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, m_pInputFileFilter, NULL, pDVMux);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// connect file source audio stream to DV MUX
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, m_pInputFileFilter, NULL, pDVMux);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// connect DV MUX to tee
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, pDVMux, NULL, pInfTee);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// connect one branch of tee to MSDV transmit
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, pInfTee, NULL, m_pDeviceFilter);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// render other branch of tee to audio & video preview
|
|
SmartPtr<IPin> pOut;
|
|
hr = m_pCaptureGraphBuilder->FindPin(pInfTee, PINDIR_OUTPUT, NULL, NULL, TRUE, 0, &pOut);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pCaptureGraphBuilder->FindPin failed."), hr);
|
|
|
|
hr = m_pGraph->Render(pOut);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_Type2::m_pGraph->Render failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------------------------------------
|
|
Routine: DV_MakeFileToDvGraph_NoPre_Type2
|
|
Purpose: Builds and runs the File to DV graph
|
|
Arguments: None
|
|
Returns: HRESULT as apropriate
|
|
Notes: This is a transmit only graph for DV Type 2 AVI files
|
|
This graph looks like this:
|
|
FileSource->AVI_Splitter(vid) ->DVMuxer(vid)----->DV_Camera
|
|
AVI_Splitter(aud)->DVMuxer(aud)
|
|
---------------------------------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::MakeFileToDvGraph_NoPre_Type2(TCHAR* InputFileName)
|
|
{
|
|
m_iGraphType = GRAPH_FILE_TO_DV_NOPRE_TYPE2;
|
|
HRESULT hr = S_OK;
|
|
|
|
ASSERT(InputFileName[0]);
|
|
|
|
SmartPtr< IBaseFilter > pDVMux = NULL;
|
|
hr = CoCreateInstance(CLSID_DVMux, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<PVOID *>(&pDVMux));
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_NoPre_Type2::CoCreate DVMux failed."), hr);
|
|
|
|
// Add the file as source filter to the graph
|
|
hr = m_pGraph->AddSourceFilter(InputFileName, InputFileName, &m_pInputFileFilter);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_NoPre_Type2::m_pGraph->AddSourceFilter failed."), hr);
|
|
|
|
// Add the DVMuxer to the graph
|
|
hr = m_pGraph->AddFilter(pDVMux, L"DV Muxer");
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_NoPre_Type2::add DVMux failed."), hr);
|
|
|
|
// the graph we need to build is: ASYNC reader --> AVI SPLITTER --> DV MUX --> MSDV
|
|
//
|
|
// connect file video stream to DV MUX
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, m_pInputFileFilter, NULL, pDVMux);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_NoPre_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// connect file audio stream to DV MUX
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, m_pInputFileFilter, NULL, pDVMux);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_NoPre_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
// connect DV MUX to DV Transmit
|
|
hr = m_pCaptureGraphBuilder->RenderStream(NULL, NULL, pDVMux, NULL, m_pDeviceFilter);
|
|
CHECK_ERROR( TEXT("CDVGraph::MakeFileToDvGraph_NoPre_Type2::m_pCaptureGraphBuilder->RenderStream failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: DV_SetAviOptions
|
|
Purpose: Routine for changing AVI Mux properties. In this sample,
|
|
we just set a few options.
|
|
These options could be set through the Avi Mux property sheet,
|
|
or through a separate dialog.
|
|
Arguments: Pointer to the AVI renderer (from SetOutputFileName())
|
|
Returns: HRESULT as appropriate
|
|
Notes:
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::SetAviOptions(IBaseFilter *ppf, InterleavingMode INTERLEAVE_MODE)
|
|
{
|
|
HRESULT hr;
|
|
SmartPtr<IConfigAviMux> pMux = NULL;
|
|
SmartPtr<IConfigInterleaving> pInterleaving = NULL;
|
|
|
|
ASSERT(ppf);
|
|
if (!ppf)
|
|
return E_POINTER;
|
|
|
|
// QI for interface AVI Muxer
|
|
hr = ppf->QueryInterface(IID_IConfigAviMux, reinterpret_cast<PVOID *>(&pMux));
|
|
CHECK_ERROR( TEXT("CDVGraph::SetAviOptions::QI IConfigAviMux failed."), hr);
|
|
|
|
hr = pMux->SetOutputCompatibilityIndex(TRUE);
|
|
CHECK_ERROR( TEXT("CDVGraph::SetAviOptions::pMux->SetOutputCompatibilityIndex failed."), hr);
|
|
|
|
// QI for interface Interleaving
|
|
hr = ppf->QueryInterface(IID_IConfigInterleaving, reinterpret_cast<PVOID *>(&pInterleaving));
|
|
CHECK_ERROR( TEXT("CDVGraph::SetAviOptions::QI IConfigInterleaving failed."), hr);
|
|
|
|
// put the interleaving mode (full, none, half)
|
|
hr = pInterleaving->put_Mode(INTERLEAVE_MODE);
|
|
CHECK_ERROR( TEXT("CDVGraph::SetAviOptions::pInterleaving->put_Mode failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
| Function: CAVCGraph::RemoveFilters
|
|
| Purpose: Tears downs the graph from the specified filter onwards.
|
|
| Arguments: Filter to be removed downstream from
|
|
| Returns: None
|
|
| Notes: The filter specified in the argument is not removed from the filtergraph; This is a recursively calling function
|
|
\----------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::RemoveFilters(IBaseFilter *pFilter, BOOL bRemoveDownStream)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IPin *pPin = NULL, *pToPin = NULL;
|
|
IEnumPins *pEnumPins = NULL;
|
|
|
|
ULONG uFetched = 0;
|
|
PIN_INFO PinInfo;
|
|
|
|
ASSERT(m_pGraph);
|
|
|
|
// Validating the the pointer to the Filter is not null
|
|
if(!pFilter)
|
|
{
|
|
Dump( TEXT("CAVCGraph::RemoveFilters():: Invalid Argument:: Invalid filter to remove from") );
|
|
return E_FAIL;
|
|
}
|
|
|
|
// enumerate all the pins on this filter
|
|
// reset the enumerator to the first pin
|
|
hr = pFilter->EnumPins(&pEnumPins);
|
|
CHECK_ERROR( TEXT("CAVCGraph::RemoveFilters():: Could not enumerate pins on the filter to be removed.hr=%#x"), hr);
|
|
pEnumPins->Reset();
|
|
|
|
// Loop through all the pins of the filter
|
|
while( SUCCEEDED(pEnumPins->Next(1, &pPin, &uFetched)) && pPin )
|
|
{
|
|
// Get the pin & its pin_info struct that this filter's pin is connected to
|
|
hr = pPin->ConnectedTo(&pToPin);
|
|
if(SUCCEEDED(hr) &&pToPin )
|
|
{
|
|
hr = pToPin->QueryPinInfo(&PinInfo);
|
|
CHECK_ERROR( TEXT("pToPin->QueryPinInfo failed.hr=%#x"), hr);
|
|
|
|
// Check that this ConnectedTo Pin is a input pin thus validating that our filter's pin is an output pin
|
|
if(PinInfo.dir == PINDIR_INPUT && bRemoveDownStream)
|
|
{
|
|
// thus we have a pin on the downstream filter so remove everything downstream of that filter recursively
|
|
RemoveFilters(PinInfo.pFilter, bRemoveDownStream);
|
|
// Disconnect the two pins and remove the downstream filter
|
|
m_pGraph->Disconnect(pToPin);
|
|
m_pGraph->Disconnect(pPin);
|
|
|
|
//always leave the Camera filter in the graph
|
|
if (PinInfo.pFilter != m_pDeviceFilter)
|
|
{
|
|
hr = m_pGraph->RemoveFilter(PinInfo.pFilter);
|
|
}
|
|
}
|
|
|
|
SAFE_RELEASE(PinInfo.pFilter);
|
|
SAFE_RELEASE(pToPin);
|
|
}
|
|
SAFE_RELEASE(pPin);
|
|
}
|
|
|
|
SAFE_RELEASE(pEnumPins);
|
|
return S_OK;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: DV_StartGraph
|
|
Purpose: Starts the Filter Graph
|
|
Arguments: None
|
|
Returns: HResult as appropriate
|
|
Notes:
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::StartGraph(void)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// start the graph
|
|
hr = m_pMediaControl->Run();
|
|
if ( FAILED(hr))
|
|
{
|
|
Dump(TEXT("CDVGraph::StartGraph::m_pMediaControl->Run() Failed!"));
|
|
// stop parts that ran
|
|
m_pMediaControl->Stop();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: DV_PauseGraph
|
|
Purpose: Starts the Filter Graph
|
|
Arguments: None
|
|
Returns: HResult as appropriate
|
|
Notes:
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::PauseGraph(void)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Pause the graph
|
|
hr = m_pMediaControl->Pause();
|
|
if ( FAILED(hr))
|
|
{
|
|
Dump(TEXT("CDVGraph::StartGraph::m_pMediaControl->Pause() Failed!"));
|
|
// stop parts that ran
|
|
m_pMediaControl->Stop();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: DV_StopGraph
|
|
Purpose: Starts the Filter Graph
|
|
Arguments: None
|
|
Returns: HResult as appropriate
|
|
Notes:
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::StopGraph(void)
|
|
{
|
|
HRESULT hr = m_pMediaControl->Stop();
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: CDVGraph::getDroppedFrameNum
|
|
Purpose: Callback proc to display dropped frame info
|
|
Arguments: [in]DWORD is the graph in transmit mode
|
|
Arguments: [out]long* number of dropped frames
|
|
Arguments: [out]long* number of not dropped frames
|
|
Returns: None
|
|
Notes: For both Capture & Transmit graphs
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::getDroppedFrameNum( BOOL *bIsModeTransmit, long* pDropped, long* pNotdropped)
|
|
{
|
|
HRESULT hr;
|
|
SmartPtr<IPin> pAVIn = NULL;
|
|
|
|
SAFE_RELEASE(m_pDroppedFrames);
|
|
ASSERT(bIsModeTransmit != NULL);
|
|
|
|
if (!bIsModeTransmit)
|
|
return E_POINTER;
|
|
|
|
// capture
|
|
if (GRAPH_DV_TO_FILE == m_iGraphType || GRAPH_DV_TO_FILE_NOPRE == m_iGraphType ||
|
|
GRAPH_DV_TO_FILE_TYPE2 == m_iGraphType || GRAPH_DV_TO_FILE_NOPRE_TYPE2 == m_iGraphType)
|
|
*bIsModeTransmit = FALSE;
|
|
|
|
// transmit
|
|
else if (GRAPH_FILE_TO_DV == m_iGraphType || GRAPH_FILE_TO_DV_NOPRE == m_iGraphType ||
|
|
GRAPH_FILE_TO_DV_TYPE2 == m_iGraphType || GRAPH_FILE_TO_DV_NOPRE_TYPE2 == m_iGraphType)
|
|
*bIsModeTransmit = TRUE;
|
|
|
|
if( *bIsModeTransmit)
|
|
{
|
|
hr = m_pCaptureGraphBuilder->FindPin(m_pDeviceFilter, PINDIR_INPUT, NULL, NULL, FALSE, 0, &pAVIn);
|
|
if(FAILED(hr) || pAVIn == NULL)
|
|
{
|
|
Dump(TEXT("CDVGraph::getDroppedFrameNum::m_pCaptureGraphBuilder->FindPin Failed!"));
|
|
return hr;
|
|
}
|
|
|
|
hr = pAVIn->QueryInterface(IID_IAMDroppedFrames, reinterpret_cast<PVOID *>(&m_pDroppedFrames));
|
|
CHECK_ERROR( TEXT("CDVGraph::getDroppedFrameNum::QI IAMDroppedFrames failed."), hr);
|
|
}
|
|
else
|
|
{
|
|
hr = m_pCaptureGraphBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
|
|
&MEDIATYPE_Interleaved,
|
|
m_pDeviceFilter, IID_IAMDroppedFrames,
|
|
reinterpret_cast<PVOID *>(&m_pDroppedFrames));
|
|
CHECK_ERROR( TEXT("CDVGraph::getDroppedFrameNum::m_pCaptureGraphBuilder->FindInterface failed."), hr);
|
|
}
|
|
|
|
hr = m_pDroppedFrames->GetNumDropped(pDropped);
|
|
CHECK_ERROR( TEXT("CDVGraph::getDroppedFrameNum::m_pDroppedFrames->GetNumDropped failed."), hr);
|
|
|
|
hr = m_pDroppedFrames->GetNumNotDropped(pNotdropped);
|
|
CHECK_ERROR( TEXT("CDVGraph::getDroppedFrameNum::m_pDroppedFrames->GetNumDropped failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: ChangeFrameRate
|
|
Purpose: Controls discard or not half of the frames in the video stream
|
|
Arguments: None
|
|
Returns: HRESULT
|
|
Notes: For NTSC, the frame rate is reduced from 30 frames per second (fps) to 15 fps.
|
|
For PAL, the frame rate is reduced from 25 fps to 12.5 fps.
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::ChangeFrameRate(BOOL bHalfFrameRate)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
SmartPtr<IBaseFilter> pDVSplitter;
|
|
SmartPtr<IDVSplitter> pIDVSplitter = NULL;
|
|
|
|
/* Obtain the dv docoder's IBaseFilter interface. */
|
|
hr = m_pGraph->FindFilterByName(L"DV Splitter", &pDVSplitter) ;
|
|
CHECK_ERROR( TEXT("CDVGraph::ChangeFrameRate()::m_pGraph->FindFilterByName failed."), hr);
|
|
|
|
hr = pDVSplitter->QueryInterface(IID_IDVSplitter, reinterpret_cast<PVOID *>(&pIDVSplitter));
|
|
CHECK_ERROR( TEXT("CDVGraph::ChangeFrameRate()::QI IDVSplitter failed."), hr);
|
|
|
|
if(bHalfFrameRate)
|
|
hr = pIDVSplitter->DiscardAlternateVideoFrames(1); // if the value is non-zero, discards alternate frames.
|
|
else
|
|
hr = pIDVSplitter->DiscardAlternateVideoFrames(0); // If the value is zero, the filter delivers every frame.
|
|
CHECK_ERROR( TEXT("CDVGraph::ChangeFrameRate()::pIDVSplitter->DiscardAlternateVideoFrames failed."), hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------------
|
|
Routine: CDVGraph::GetResolutionFromDVDecoderPropertyPage
|
|
Purpose: controls discard or not half of the frames in the video stream
|
|
Arguments: None
|
|
Returns: HRESULT
|
|
Notes: For NTSC, the frame rate is reduced from 30 frames per second (fps) to 15 fps.
|
|
For PAL, the frame rate is reduced from 25 fps to 12.5 fps.
|
|
---------------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::GetResolutionFromDVDecoderPropertyPage( HWND hwndApp, BOOL bChangeResolution)
|
|
{
|
|
HRESULT hr;
|
|
IBaseFilter *pDVDecoder;
|
|
IIPDVDec *pIPDVDec;
|
|
|
|
/* Obtain the dv docoder's IBaseFilter interface. */
|
|
hr = m_pGraph->FindFilterByName(L"DV Video Decoder", &pDVDecoder) ;
|
|
CHECK_ERROR( TEXT("CDVGraph::GetResolutionFromDVDecoderPropertyPage()::m_pGraph->FindFilterByName failed."), hr);
|
|
|
|
SmartPtr<ISpecifyPropertyPages> pProp;
|
|
hr = pDVDecoder->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pProp);
|
|
CHECK_ERROR( TEXT("CDVGraph::GetResolutionFromDVDecoderPropertyPage()::QI ISpecifyPropertyPages failed."), hr);
|
|
|
|
// Get the filter's name and IUnknown pointer.
|
|
FILTER_INFO FilterInfo;
|
|
hr = pDVDecoder->QueryFilterInfo(&FilterInfo);
|
|
CHECK_ERROR( TEXT("CDVGraph::GetResolutionFromDVDecoderPropertyPage()::pDVDecoder->QueryFilterInfo failed."), hr);
|
|
|
|
// Show the page.
|
|
if(bChangeResolution)
|
|
{
|
|
CAUUID caGUID;
|
|
pProp->GetPages(&caGUID);
|
|
|
|
OleCreatePropertyFrame(
|
|
hwndApp, // Parent window
|
|
0, 0, // (Reserved)
|
|
FilterInfo.achName, // Caption for the dialog box
|
|
1, // Number of objects (just the filter)
|
|
(IUnknown **)&pDVDecoder, // Array of object pointers.
|
|
caGUID.cElems, // Number of property pages
|
|
caGUID.pElems, // Array of property page CLSIDs
|
|
0, // Locale identifier
|
|
0, NULL // Reserved
|
|
);
|
|
CoTaskMemFree(caGUID.pElems);
|
|
}
|
|
|
|
// Clean up.
|
|
FilterInfo.pGraph->Release();
|
|
|
|
hr = pDVDecoder->QueryInterface(IID_IIPDVDec, reinterpret_cast<PVOID *>(&pIPDVDec));
|
|
CHECK_ERROR( TEXT("CDVGraph::GetResolutionFromDVDecoderPropertyPage()::QI IIPDVDec failed."), hr);
|
|
|
|
hr = pIPDVDec->get_IPDisplay(reinterpret_cast <int *>(&m_DVResolution));
|
|
CHECK_ERROR( TEXT("CDVGraph::GetResolutionFromDVDecoderPropertyPage()::pIPDVDec->get_IPDisplay failed."), hr);
|
|
|
|
SAFE_RELEASE( pDVDecoder );
|
|
SAFE_RELEASE( pIPDVDec );
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: CDVGraph::GetResolutionFromDVDecoderPropertyPage
|
|
Purpose: controls discard or not half of the frames in the video stream
|
|
Arguments: None
|
|
Returns: HRESULT
|
|
Notes: For NTSC, the frame rate is reduced from 30 frames per second (fps) to 15 fps.
|
|
For PAL, the frame rate is reduced from 25 fps to 12.5 fps.
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::GetVideoWindowDimensions (int* pWidth, int *pHeight, BOOL bChangeResolution,HWND hwndApp)
|
|
{
|
|
HRESULT hr;
|
|
|
|
ASSERT(pWidth != NULL);
|
|
ASSERT(pHeight != NULL);
|
|
if (!pWidth || !pHeight)
|
|
return E_POINTER;
|
|
|
|
hr = GetResolutionFromDVDecoderPropertyPage( hwndApp, bChangeResolution );
|
|
CHECK_ERROR( TEXT("CDVGraph::GetVideoWindowDimensions()::GetResolutionFromDVDecoderPropertyPage() failed."), hr);
|
|
|
|
switch (m_DVResolution)
|
|
{
|
|
case DVRESOLUTION_FULL:
|
|
*pWidth = DVENCODER_WIDTH;
|
|
if (DVENCODERVIDEOFORMAT_PAL == m_VideoFormat)
|
|
*pHeight = PAL_DVENCODER_HEIGHT;
|
|
else if (DVENCODERVIDEOFORMAT_NTSC == m_VideoFormat)
|
|
*pHeight = NTSC_DVENCODER_HEIGHT;
|
|
break;
|
|
|
|
case DVRESOLUTION_HALF:
|
|
*pWidth = DVENCODER_WIDTH/2;
|
|
if (DVENCODERVIDEOFORMAT_PAL == m_VideoFormat)
|
|
*pHeight = PAL_DVENCODER_HEIGHT/2;
|
|
else if (DVENCODERVIDEOFORMAT_NTSC == m_VideoFormat)
|
|
*pHeight = NTSC_DVENCODER_HEIGHT/2;
|
|
break;
|
|
|
|
case DVRESOLUTION_QUARTER:
|
|
*pWidth = DVENCODER_WIDTH/4;
|
|
if (DVENCODERVIDEOFORMAT_PAL == m_VideoFormat)
|
|
*pHeight = PAL_DVENCODER_HEIGHT/4;
|
|
else if (DVENCODERVIDEOFORMAT_NTSC == m_VideoFormat)
|
|
*pHeight = NTSC_DVENCODER_HEIGHT/4;
|
|
break;
|
|
|
|
case DVRESOLUTION_DC:
|
|
*pWidth = 88;
|
|
if (DVENCODERVIDEOFORMAT_PAL == m_VideoFormat)
|
|
*pHeight = PAL_DVENCODER_HEIGHT/8;
|
|
else if (DVENCODERVIDEOFORMAT_NTSC == m_VideoFormat)
|
|
*pHeight = NTSC_DVENCODER_HEIGHT/8;
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
Routine: DV_SeekATN
|
|
Purpose: ATN Seek function - uses GetTransportBasicParameters to send RAW AVC command
|
|
Arguments: None
|
|
Returns: TRUE if successful
|
|
Notes: This is Absolute Track Number Seek not TimeCode Seek but
|
|
uses the timecode display as input
|
|
------------------------------------------------------------------------*/
|
|
HRESULT CDVGraph::SeekATN(int iHr, int iMn, int iSc, int iFr)
|
|
{
|
|
BOOL bStatus = FALSE;
|
|
HRESULT hr = S_OK;
|
|
ULONG ulTrackNumToSearch;
|
|
long iCnt = 8;
|
|
|
|
if (DVENCODERVIDEOFORMAT_PAL == m_VideoFormat && (iFr > 25) )
|
|
{
|
|
Dump(TEXT("Invalid Parameter - Frame should be less than 25 for PAL"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (DVENCODERVIDEOFORMAT_NTSC == m_VideoFormat && (iFr > 30) )
|
|
{
|
|
Dump(TEXT("Invalid Parameter - Frame should be less than 30 for NTSC"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
|
|
// ATN Seek Raw AVC Command
|
|
BYTE RawAVCPkt[8] = {0x00, 0x20, 0x52, 0x20, 0xff, 0xff, 0xff, 0xff};
|
|
|
|
if ((iHr < 24) && (iHr >= 0) && (iMn < 60) && (iMn >= 0) && (iSc < 60) && (iSc >= 0))
|
|
{
|
|
//Calculate the ATN
|
|
if (m_AvgTimePerFrame == 40)
|
|
{
|
|
ulTrackNumToSearch = ((iMn * 60 + iSc) * 25 + iFr) * 12 * 2;
|
|
}
|
|
else
|
|
{
|
|
// Drop two frame every minutes
|
|
ulTrackNumToSearch = ((iMn * 60 + iSc) * 30 + iFr - ((iMn - (iMn / 10)) * 2)) * 10 * 2;
|
|
}
|
|
// Update the Raw AVC Command query
|
|
RawAVCPkt[4] = (BYTE) (ulTrackNumToSearch & 0x000000ff);
|
|
RawAVCPkt[5] = (BYTE) ((ulTrackNumToSearch & 0x0000ff00) >> 8);
|
|
RawAVCPkt[6] = (BYTE) ((ulTrackNumToSearch & 0x00ff0000) >> 16);
|
|
|
|
// RAW AVC Call
|
|
hr = m_pIAMExtTransport->GetTransportBasicParameters(ED_RAW_EXT_DEV_CMD, &iCnt, (LPOLESTR *)RawAVCPkt);
|
|
if ((HRESULT) ERROR_TIMEOUT == hr)
|
|
OutputDebugString(TEXT(" ATN Seek returns ERROR_TIMEOUT"));
|
|
else if ((HRESULT) ERROR_REQ_NOT_ACCEP == hr)
|
|
OutputDebugString(TEXT(" ATN Seek returns ERROR_REQ_NOT_ACCEP"));
|
|
else if ((HRESULT) ERROR_NOT_SUPPORTED == hr)
|
|
OutputDebugString(TEXT(" ATN Seek returns ERROR_NOT_SUPPORTED"));
|
|
else if ((HRESULT) ERROR_REQUEST_ABORTED == hr)
|
|
OutputDebugString(TEXT(" ATN Seek returns ERROR_REQUEST_ABORTED "));
|
|
}
|
|
else
|
|
{
|
|
Dump(TEXT("Invalid Parameter - Time entered should be:\nHour:Minute:Second:Frame"));
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// helper function
|
|
BOOL IsDeviceOutputDV(IBaseFilter * pFilter)
|
|
{
|
|
if (!pFilter) return FALSE;
|
|
|
|
IEnumPins *pEnum = 0;
|
|
IPin *pPin = 0;
|
|
ULONG ul ;
|
|
BOOL bFound = FALSE;
|
|
|
|
HRESULT hr = pFilter->EnumPins(&pEnum);
|
|
if (FAILED(hr)) return FALSE;
|
|
|
|
while (S_OK == pEnum->Next(1, &pPin, 0))
|
|
{
|
|
// See if this pin matches the specified direction.
|
|
PIN_DIRECTION ThisPinDir;
|
|
hr = pPin->QueryDirection(&ThisPinDir);
|
|
if (FAILED(hr)) {
|
|
SAFE_RELEASE(pPin);
|
|
break;
|
|
}
|
|
|
|
if (ThisPinDir == PINDIR_OUTPUT)
|
|
{
|
|
IEnumMediaTypes* pTypeEnum;
|
|
hr = pPin->EnumMediaTypes (&pTypeEnum);
|
|
|
|
AM_MEDIA_TYPE* pMediaType;
|
|
// Loop thru' media type list for a match
|
|
do {
|
|
hr = pTypeEnum->Next(1, &pMediaType, &ul) ;
|
|
if (FAILED(hr) || 0 == ul) {
|
|
SAFE_RELEASE(pPin);
|
|
break ;
|
|
}
|
|
|
|
if (pMediaType->subtype == MEDIASUBTYPE_dvsd ||
|
|
pMediaType->subtype == MEDIASUBTYPE_DVSD) {
|
|
bFound = TRUE;
|
|
SAFE_RELEASE(pPin);
|
|
DeleteMediaType( pMediaType );
|
|
|
|
pTypeEnum->Release();
|
|
SAFE_RELEASE(pEnum);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
DeleteMediaType( pMediaType );
|
|
|
|
} while (!bFound) ; // until the reqd one is found
|
|
|
|
pTypeEnum->Release();
|
|
}
|
|
}
|
|
|
|
SAFE_RELEASE(pPin);
|
|
SAFE_RELEASE(pEnum);
|
|
|
|
// Did not find a matching filter.
|
|
return FALSE;
|
|
} |