//
// Copyright (c) 1998-2002 B2C2, Incorporated.  All Rights Reserved.
//
// THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF B2C2, INCORPORATED.
// The copyright notice above does not evidence any
// actual or intended publication of such source code.
//
// This file is proprietary source code of B2C2, Incorporated. and is released pursuant to and
// subject to the restrictions of the non-disclosure agreement and license contract entered
// into by the parties.
//
//
// TestDataFilter.cpp
//
//
// Version 1.9.0 - May 8, 2002
//
// Changes:
//
// 1 - Implemented use of B2C2MPEG2Adapter class.
//
//
// Version 0.2 - April 17, 2001
//
// Changes:
//
// 1 - Added support for setting multiple data PIDs (up to 4).
// 2 - Modified filenaming convention.  Data files are now always
//     created in the current directory, and have the extension of
//     the PID with which they are associated.

#include <stdio.h>

#include <Dshow.h>
#include <initguid.h>

#include "B2C2_Defs.h"
#include "b2c2mpeg2adapter.h"
#include "IB2C2MPEG2DataCtrl.h"
#include "IB2C2MPEG2TunerCtrl.h"

// Command line usage message

#define	USAGE_MSG "\
\n\
Usage: TestDataFilter Flags [Options]\n\
\n\
Note: Flags and Options must be separated from values with blanks;\n\
      e.g. TestDataFilter -i c\n\
\n\
Flags:\n\
-i    Tuner type; valid values are: c (cable), s (satellite)\n\
      t (Terrestrial DVB) and a (Terrestrial ATSC) \n\
-f    Frequency (Transponder) in MHz (c, s); e.g. 350\n\
-s    Symbol Rate in Ks/s (c, s); e.g. 6111\n\
-m    Modulation in QAM (c); valid values are:\n\
      4, 16, 32, 64, 128, 256\n\
-l    LNB Frequency in MHz (s); note: must be less than Transponder\n\
      Frequency specified with -f\n\
-e    FEC (s); valid values are:\n\
      1/2, 2/3, 3/4, 5/6, 7/8, auto\n\
-o    Polarity (s); valid values are: h (horizontal), v (vertical)\n\
-k    LNB Selection in KHz(s); valid values are: 0, 22, 33, 44\n\
-d    Diseqc (s); valid values are:\n\
      n (none), a, b, a/a, a/b, b/a, b/b\n\
-g    Guard Interval (t): valid values are:\n\
	  1/32, 1/16, 1/8, 1/4, Auto\n\
-b    Bandwidth (t): valid values are:\n\
	  6, 7, 8\n\
-p0   For Filter Pin 0, assign data PID as integer or hex. (c, s); e.g. 164,\n\
      0xa4 or 0XA4.  For multiple data PIDs, specify a separate -p0 option;\n\
      e.g. -p0 17 -p0 18. \n\
-p1   For Filter Pin 1, assign data PID. \n\
-p2   For Filter Pin 2, assign data PID. \n\
-p3   For Filter Pin 3, assign data PID. \n\
\n\
Options:\n\
-t    Time duration in seconds for dumping to file; default is 10 seconds\n\
-h    Help\n"

// Symbols related to filter settings

#define	SLEEP_LENGTH			10L				// SParams.lSleepLength value default
#define DUMP_FILE_NAME			"PID"			// SParams.szDumpFilePathname string default
#define DIRPATHNAME_SZ			1024			// SParams.szDirName string size
#define DUMPFILEPATHNAME_SZ		1024			// SParams.szDumpFilePathname string size

#define MAX_DATA_PIDS_PER_PIN	39

typedef enum 
{
	DATA_PIN_0,
	DATA_PIN_1,
	DATA_PIN_2,
	DATA_PIN_3
} tDataPinIndex;

// Struct used for passing tuner parameters

typedef struct
{	
	long			lTunerType;
	bool			bTunerTypeSet;

	long			lFrequency;
	bool			bFrequencySet;
	long			lSymbolRate;
	bool			bSymbolRateSet;
	eModulation		eModulation;
	bool			bModulationSet;
	bool			bGuardIntervalSet;
	long			lLNB;
	bool			bLNBSet;
	eFEC			eFEC;
	bool			bFECSet;
	ePolarity		ePolarity;
	bool			bPolaritySet;
	eLNBSelection	eLNBSelection;
	bool			bLNBSelectionSet;
	eDiseqc			eDiseqc;
	bool			bDiseqcSet;
	eGuardInterval	eGuardInterval;
	eBandwidth		eBandwidth;
	bool			bBandwidthSet;

	struct 
	{
		long			lPidCount;
		long			lDataPIDPin[MAX_DATA_PIDS_PER_PIN];
		bool			bDataPIDPinSet;
		char			szDumpFilePathname[DUMPFILEPATHNAME_SZ];
	}DataPin[B2C2_FILTER_MAX_TS_PINS];

	long			lTotalPidCount;

	long			lSleepLength;
	char			szDirName[DIRPATHNAME_SZ];

	bool			bUseOldAddPidMethods;
	bool			bAddDeletePidsSequentially;
} SParams;

// Dump Filter GUID

// NB: This ID can also be discovered programmatically by enumerating
// the registered filters on the system.  In this sample program, we are
// chosing to reference it directly.

// { 36a5f770-fe4c-11ce-a8ed-00aa002feab5 }
DEFINE_GUID(CLSID_Dump,
0x36a5f770, 0xfe4c, 0x11ce, 0xa8, 0xed, 0x00, 0xaa, 0x00, 0x2f, 0xea, 0xb5);

// Prototypes

int CheckCommandLineArgs (int argc, char * argv[], SParams * psParams);
void CallSysFreeString (BSTR*);
int AddPidToPinList (SParams* psParams, char* pszPid, int iDataPinIndex);

// **********************************************************************
// *** main ()
// **********************************************************************

 int main (int argc, char * argv[])
{
	HRESULT					hr;

	SParams					sParams;

	IB2C2MPEG2DataCtrl3 	*pB2C2FilterDataCtrl = NULL;
	IB2C2MPEG2TunerCtrl3 	*pB2C2FilterTunerCtrl = NULL;
	IFileSinkFilter			*pFileSink = NULL;
	IMediaControl			*pMediaControl = NULL;

	OLECHAR					szOleFilePathname[DUMPFILEPATHNAME_SZ];
	BSTR					bstrFileName[B2C2_FILTER_MAX_TS_PINS];

	long					lCount;

	// Initializations.

	sParams.lTotalPidCount = 0;
	sParams.bTunerTypeSet = FALSE;

	sParams.bFrequencySet = FALSE;
	sParams.bSymbolRateSet = FALSE;
	sParams.bLNBSet = FALSE;
	sParams.bFECSet = FALSE;
	sParams.bPolaritySet = FALSE;
	sParams.bLNBSelectionSet = FALSE;
	sParams.bDiseqcSet = FALSE;
	sParams.bModulationSet = FALSE;
	sParams.bGuardIntervalSet = FALSE;
	sParams.bBandwidthSet = FALSE;

	for (int iDataPinIndex = 0 ; iDataPinIndex < B2C2_FILTER_MAX_TS_PINS ; iDataPinIndex++)
	{
		bstrFileName[iDataPinIndex] = NULL;

		sParams.DataPin[iDataPinIndex].lPidCount = 0;
	}

	sParams.lSleepLength = SLEEP_LENGTH;

	sParams.bUseOldAddPidMethods = FALSE;
	sParams.bAddDeletePidsSequentially = FALSE;

	if (GetCurrentDirectory (DIRPATHNAME_SZ, sParams.szDirName) == 0)
	{
		fprintf (stderr, "\nError: Internal error; GetCurrentDirectory call failed\n");

		return 1;	// *** FUNCTION EXIT POINT
	}

	// Check command line arguments.

	if (CheckCommandLineArgs (argc, argv, &sParams) < 0)
	{
		return 1;	// *** FUNCTION EXIT POINT
	}

	if (sParams.bTunerTypeSet == FALSE)
	{
		fprintf (stderr, "\nError: Tuner type must be specified\n");
		fprintf (stderr, USAGE_MSG);

		return 1;	// *** FUNCTION EXIT POINT
	}

	// **********************************************************************
	// *** Create B2C2 MPEG 2 Adapter object
	// **********************************************************************

	B2C2MPEG2Adapter b2c2Adapter ("");	// name not required on Windows

	hr = b2c2Adapter.Initialize ();

	if (FAILED (hr)) 
	{
		fprintf (stderr, "%s failed (#%08X)!\n", b2c2Adapter.GetLastErrorText (), b2c2Adapter.GetLastError ());

		return 1;	// *** FUNCTION EXIT POINT
	}

	// Query interfaces; if Initialize succeeded, interfaces are available

	pB2C2FilterTunerCtrl = b2c2Adapter.GetTunerControl ();
	pB2C2FilterDataCtrl = b2c2Adapter.GetDataControl ();

	// ***************************************************************************************
	// *** Set B2C2 MPEG2 Filter Data PIDs (do this before locating the output data pin).
	// ***************************************************************************************

	// Make sure at least one PID was set

	if (sParams.lTotalPidCount == 0)
	{
		fprintf (stderr, "\nError: At least one Data PID should be specified using a -pd option\n");
		fprintf (stderr, USAGE_MSG);

		return 1;	// *** FUNCTION EXIT POINT
	}

	// Get max. PID count

	hr = pB2C2FilterDataCtrl->GetMaxPIDCount (&lCount);

	if (FAILED (hr))
	{
		fprintf (stderr, "B2C2 MPEG2 Filter Data Ctrl. Interface GetMaxPIDCount method failed (#%08X)!\n", hr);

		return hr;	// *** FUNCTION EXIT POINT
	}

	// **********************************************************************
	// *** Configure tuner
	// **********************************************************************

	// Set tuner parameters depending on type of tuner specified.

	switch (sParams.lTunerType)
	{

	// **********************************************************************
	// *** Terrestrial DVB
	// **********************************************************************

	case TUNER_TERRESTRIAL_DVB:
	{
		// If frequency and bandwidth are not set, do not attempt to tune.
		if (sParams.bFrequencySet == FALSE ||
			sParams.bBandwidthSet == FALSE )
		{
			fprintf (stderr, "\nError: One or more flags have not been specified for this tuner type\n");
			fprintf (stderr, USAGE_MSG);

			CallSysFreeString (bstrFileName);
			return 1;	// *** FUNCTION EXIT POINT
		}

		// Set the specified Frequency
		pB2C2FilterTunerCtrl->SetFrequencyKHz (sParams.lFrequency);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetFrequency method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		// Check to set the specified Guard Interval
		if (sParams.bGuardIntervalSet == TRUE)
		{
			pB2C2FilterTunerCtrl->SetGuardInterval (sParams.eGuardInterval);
			
			if (FAILED (hr))
			{
				fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetGuardInterval method failed (#%08X)!\n", hr);

				CallSysFreeString (bstrFileName);
				return hr;	// *** FUNCTION EXIT POINT
			}
		}

		// Set the specified Bandwidth
		pB2C2FilterTunerCtrl->SetBandwidth(sParams.eBandwidth);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetGuardInterval method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}
		break;
	}

	// **********************************************************************
	// *** ATSC
	// **********************************************************************
	
	case TUNER_TERRESTRIAL_ATSC:

		// Make sure all flags have been set

		if (sParams.bFrequencySet == FALSE)
		{
			fprintf (stderr, "\nError: Frequency has not been specified\n");
			fprintf (stderr, USAGE_MSG);

			CallSysFreeString (bstrFileName);
			return 1;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetFrequencyKHz (sParams.lFrequency);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetFrequency method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		break;

	// **********************************************************************
	// *** Cable
	// **********************************************************************
	
	case TUNER_CABLE:

		// Make sure all flags have been set

		if (sParams.bFrequencySet == FALSE ||
			sParams.bSymbolRateSet == FALSE ||
			sParams.bModulationSet == FALSE)
		{
			fprintf (stderr, "\nError: One or more flags have not been specified for this tuner type\n");
			fprintf (stderr, USAGE_MSG);

			CallSysFreeString (bstrFileName);
			return 1;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetFrequencyKHz (sParams.lFrequency);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetFrequency method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetSymbolRate (sParams.lSymbolRate);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetSymbolRate method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetModulation (sParams.eModulation);

		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetModulation method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		break;

	// **********************************************************************
	// *** Satellite
	// **********************************************************************
	
	case TUNER_SATELLITE:

		// Make sure all flags have been set

		if (sParams.bFrequencySet == FALSE ||
			sParams.bSymbolRateSet == FALSE ||
			sParams.bLNBSet == FALSE ||
			sParams.bFECSet == FALSE ||
			sParams.bPolaritySet == FALSE ||
			sParams.bLNBSelectionSet == FALSE ||
			sParams.bDiseqcSet == FALSE)
		{
			fprintf (stderr, "\nError: One or more flags have not been specified for this tuner type\n");
			fprintf (stderr, USAGE_MSG);

			CallSysFreeString (bstrFileName);
			return 1;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetFrequencyKHz (sParams.lFrequency);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetFrequency method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetSymbolRate (sParams.lSymbolRate);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetSymbolRate method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetLnbFrequency (sParams.lLNB);

		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetLnbFrequency method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetFec (sParams.eFEC);

		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetFec method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetPolarity (sParams.ePolarity);

		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetPolarity method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetLnbKHz (sParams.eLNBSelection);

		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetLnbKHz method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		pB2C2FilterTunerCtrl->SetDiseqc (sParams.eDiseqc);

		if (FAILED (hr))
		{
			fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetDiseqc method failed (#%08X)!\n", hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		break;

	default:
		// Following is an internal error (should not happen)

		fprintf (stderr, "\nError: Internal error; incorrect tuner type value detected\n");
		fprintf (stderr, USAGE_MSG);

		CallSysFreeString (bstrFileName);
		return 1;	// *** FUNCTION EXIT POINT
	}

	// Send settings to tuner.

	hr = pB2C2FilterTunerCtrl->SetTunerStatus ();

	if (FAILED (hr))
	{
		fprintf(stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface SetTunerStatus method failed (#%08X)!\n", hr);

		CallSysFreeString (bstrFileName);
		return hr;	// *** FUNCTION EXIT POINT
	}

	// Check for lock.

	hr = pB2C2FilterTunerCtrl->CheckLock ();

	if (FAILED (hr))
	{
		fprintf (stderr, "B2C2 MPEG2 Filter Tuner Ctrl. Interface CheckLock method failed (#%08X)!\n", hr);

		CallSysFreeString (bstrFileName);
		return hr;	// *** FUNCTION EXIT POINT
	}

	// ***************************************************************************************
	// *** Add the 4 Dump Filters to Filter Graph
	// ***************************************************************************************

	for (int iDataPinIndex = 0 ; iDataPinIndex < B2C2_FILTER_MAX_TS_PINS ; iDataPinIndex++)
	{
		// ********************************************************************************
		// *** Create Dump Filter, which is the downstream sink filter,
		// *** add to Filter Graph and connect
		// *** We don't need the pointer to the interface in this applicaiton
		// ********************************************************************************

		hr = b2c2Adapter.CreateTsFilter (iDataPinIndex, 
										 CLSID_Dump, 
										 NULL);
	
		if (FAILED (hr))
		{
			fprintf (stderr, "%s on pin %d failed (#%08X)!\n", 
							 b2c2Adapter.GetLastErrorText (), iDataPinIndex, 
							 b2c2Adapter.GetLastError ());

			return hr;	// *** FUNCTION EXIT POINT
		}

		// ********************************************************************************
		// *** Set Dump Filter file pathnames for the pin:
		// ********************************************************************************

		sprintf (sParams.DataPin[iDataPinIndex].szDumpFilePathname, "%s\\PIN%d.ts",
			sParams.szDirName, iDataPinIndex);

		// 1) Get corresponding interface.

		hr = b2c2Adapter.GetTsInterfaceFilter (iDataPinIndex, 
											   IID_IFileSinkFilter, 
											   (IUnknown**) &pFileSink);
		
		if (FAILED (hr))
		{
			fprintf (stderr, "%s on pin %d failed (#%08X)!\n", 
							 b2c2Adapter.GetLastErrorText (), iDataPinIndex, 
							 b2c2Adapter.GetLastError () );

			return hr;	// *** FUNCTION EXIT POINT
		}

		// 2) Set the pathname

		// First convert to an OLE-compliant string, then use that to
		// allocate and initialize a BSTR which COM requires.

		mbstowcs (szOleFilePathname, 
				  sParams.DataPin[iDataPinIndex].szDumpFilePathname, 
				  DUMPFILEPATHNAME_SZ);

		// NB: From this point on, bstrFileName must be freed before exiting
		// program, using FREE_BSTRS() macro.

		bstrFileName[iDataPinIndex] = SysAllocString (szOleFilePathname);

		hr = pFileSink->SetFileName (bstrFileName[iDataPinIndex], NULL);

		if (FAILED (hr))
		{
			fprintf (stderr, "IFileSinkFilter Interface SetFileName method failed for Dump Filter %d (#%08X)!\n", 
							 iDataPinIndex, hr);

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}

		// ***************************************************************************************
		// Locate B2C2 Filter Data Output pins and connect to Dump Filter input pin:
		// ***************************************************************************************

		// Directly connect B2C2 Filter data output pin 0 to Dump Filter input pin.

		hr = b2c2Adapter.ConnectTsFilterInToTsOutPin (iDataPinIndex);

		if (FAILED (hr))
		{
			fprintf (stderr, "%s on pin %d failed (#%08X)!\n", 
							 b2c2Adapter.GetLastErrorText (), iDataPinIndex, 
							 b2c2Adapter.GetLastError () );

			CallSysFreeString (bstrFileName);
			return hr;	// *** FUNCTION EXIT POINT
		}
	}

	// **********************************************************************
	// *** Run Filter Graph
	// **********************************************************************

	// Get Media Control interface for control of Filter Graph.

	hr = b2c2Adapter.GetMediaControl (&pMediaControl);

	if (FAILED (hr))
	{
		fprintf (stderr, "%s failed (#%08X)!\n", b2c2Adapter.GetLastErrorText (), b2c2Adapter.GetLastError ());

		CallSysFreeString (bstrFileName);
		return hr;	// *** FUNCTION EXIT POINT
	}

	// Start data flow thru Filter Graph

	pMediaControl->Run ();

	// Check to Test reception of data on all PIDs concurrently or sequentially
	
	if (!sParams.bAddDeletePidsSequentially)
	{
		// Test reception of data on all PIDs concurrently

		for (int iDataPinIndex = 0 ; iDataPinIndex < B2C2_FILTER_MAX_TS_PINS ; iDataPinIndex++)
		{
			if (sParams.DataPin[iDataPinIndex].lPidCount )
			{
				if (sParams.bUseOldAddPidMethods)
				{
					hr = pB2C2FilterDataCtrl->AddPIDs (sParams.DataPin[iDataPinIndex].lPidCount, 
													   sParams.DataPin[iDataPinIndex].lDataPIDPin);
				}
				else
				{
					hr = pB2C2FilterDataCtrl->AddPIDsToPin (&(sParams.DataPin[iDataPinIndex].lPidCount), 
															sParams.DataPin[iDataPinIndex].lDataPIDPin,
															iDataPinIndex);
				}

				if (FAILED (hr))
				{
					fprintf(stderr, "B2C2 MPEG2 Filter Data Ctrl. Interface AddPIDs%s method failed (#%08X)!\n", 
									sParams.bUseOldAddPidMethods ? "" : "ToPin", hr);

					CallSysFreeString (bstrFileName);
					return hr;	// *** FUNCTION EXIT POINT
				}

			}
		}

		printf ("Capture data for %d second%s\n", sParams.lSleepLength,
				sParams.lSleepLength > 1 ? "s" : "");

		// Let run for specified number of seconds
		Sleep (sParams.lSleepLength * 1000L);

		// Delete all PIDs for all pins

		for (int iDataPinIndex = 0 ; iDataPinIndex < B2C2_FILTER_MAX_TS_PINS ; iDataPinIndex++)
		{
			if (sParams.DataPin[iDataPinIndex].lPidCount )
			{
				if (sParams.bUseOldAddPidMethods)
				{
					hr = pB2C2FilterDataCtrl->DeletePIDs (sParams.DataPin[iDataPinIndex].lPidCount, 
														  sParams.DataPin[iDataPinIndex].lDataPIDPin);
				}
				else
				{
					hr = pB2C2FilterDataCtrl->DeletePIDsFromPin (sParams.DataPin[iDataPinIndex].lPidCount, 
															     sParams.DataPin[iDataPinIndex].lDataPIDPin,
																 iDataPinIndex);
				}

				if (FAILED (hr))
				{
					fprintf (stderr, "B2C2 MPEG2 Filter Data Ctrl. Interface DeletePIDs%s method failed (#%08X)!\n", 
									sParams.bUseOldAddPidMethods ? "" : "FromPin", hr);

					CallSysFreeString (bstrFileName);
					return hr;	// *** FUNCTION EXIT POINT
				}
			}
		}
			
	}
	else
	{
		// Test reception of data on all PIDs sequentialy to test the dynamic PID changing.  
		// NOTE: The first Pid will be added and left running while each subsequent Pid is 
		// Added and Deleted one add a time

		bool bFirstPID = FALSE;
		long lFixedPidCount = 1;
		long lFirstPid;
		int iDataPinIndex;

		for (iDataPinIndex = 0 ; iDataPinIndex < B2C2_FILTER_MAX_TS_PINS ; iDataPinIndex++)
		{
			for (int iPidIndex = 0 ; iPidIndex < MAX_DATA_PIDS_PER_PIN; iPidIndex++)
			{
				if (iPidIndex < sParams.DataPin[iDataPinIndex].lPidCount )
				{

					long lFixedPid = sParams.DataPin[iDataPinIndex].lDataPIDPin[iPidIndex];

					if (sParams.bUseOldAddPidMethods)
					{
						hr = pB2C2FilterDataCtrl->AddPIDs (lFixedPidCount, &lFixedPid);
					}
					else
					{
						hr = pB2C2FilterDataCtrl->AddPIDsToPin (&lFixedPidCount, &lFixedPid,iDataPinIndex);
					}

					if (FAILED (hr))
					{
						fprintf (stderr, "B2C2 MPEG2 Filter Data Ctrl. Interface AddPIDs%s method failed (#%08X)!\n", 
										sParams.bUseOldAddPidMethods ? "" : "ToPin", hr);

						CallSysFreeString (bstrFileName);
						return hr;	// *** FUNCTION EXIT POINT
					}

					printf ("Capture data for %d second%s\n", sParams.lSleepLength,
							sParams.lSleepLength > 1 ? "s" : "");

					Sleep (sParams.lSleepLength * 1000L);

					// If this is the first PID, do not deleted it but saved the pid for later detetion. 
					if (bFirstPID == FALSE)
					{
						bFirstPID = TRUE;
						lFirstPid = lFixedPid;
					}
					else
					{
						if (sParams.bUseOldAddPidMethods)
						{
							hr = pB2C2FilterDataCtrl->DeletePIDs (lFixedPidCount, &lFixedPid);
						}
						else
						{
							hr = pB2C2FilterDataCtrl->DeletePIDsFromPin (lFixedPidCount, &lFixedPid, iDataPinIndex);
						}
					}
					if (FAILED (hr))
					{
						fprintf (stderr, "B2C2 MPEG2 Filter Data Ctrl. Interface DeletePIDs%s method failed (#%08X)!\n", 
										 sParams.bUseOldAddPidMethods ? "" : "FromPin", hr);

						CallSysFreeString (bstrFileName);
						return hr;	// *** FUNCTION EXIT POINT
					}
				}
			}
		}

	
		if (bFirstPID)
		{
			hr = pB2C2FilterDataCtrl->DeletePIDs (lFixedPidCount, &lFirstPid);
		}
		else
		{
			hr = pB2C2FilterDataCtrl->DeletePIDsFromPin (lFixedPidCount, &lFirstPid, iDataPinIndex);
		}
	}

	// Stop data flow thrugh Filter Graph

	pMediaControl->Stop ();

	// Make sure all objects are released from memory.

	CallSysFreeString (bstrFileName);

	return 0;
}

// **********************************************************************
// *** Check command line arguments
// **********************************************************************

int CheckCommandLineArgs (int argc, char * argv[], SParams * psParams)
{
	// Parse command line args, if any. Eliminate program name itself which
	// counts as one arg.  All options are required unless specified otherwise
	// (see Usage message and comments).

	if (--argc > 0)
	{
		// Process and set values for specified flags.

		for (int ii = 1; ii <= argc;)
		{
			_strupr (argv[ii]);

			// Tuner Type: Must be first option specified; determines applicability of
			// most of remaining options.

			if (strcmp (argv[ii], "-I") == 0)
			{
				_strupr(argv[ii+1]);

				if (strncmp (argv[ii + 1], "C", 1) == 0)
				{
					psParams->lTunerType = TUNER_CABLE;
				}
				else if (strncmp (argv[ii + 1], "S", 1) == 0)
				{
					psParams->lTunerType = TUNER_SATELLITE;
				}
				else if (strncmp (argv[ii + 1], "A", 1) == 0)
				{
					psParams->lTunerType = TUNER_TERRESTRIAL_ATSC;
				}
				else if (strncmp (argv[ii + 1], "T", 1) == 0)
				{
					psParams->lTunerType = TUNER_TERRESTRIAL_DVB;
				}
				else
				{
					fprintf (stderr, "\nError: Incorrect tuner type value: %s\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}
				
				psParams->bTunerTypeSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-F") == 0)
			{
				// Frequency: Applicable to both Cable and Satellite

				psParams->lFrequency = (int) (atof (argv[ii + 1]) * 1000);
				psParams->bFrequencySet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-S") == 0)
			{
				// Symbol Rate: Applicable to both Cable and Satellite

				psParams->lSymbolRate = atoi (argv[ii + 1]);
				psParams->bSymbolRateSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-M") == 0)
			{
				// Modulation: Cable only

				long lModulationInput = atoi (argv[ii + 1]);

				switch (lModulationInput)
				{
				case 4:
					psParams->eModulation = QAM_4;
					break;

				case 16:
					psParams->eModulation = QAM_16;
					break;
				
				case 32:
					psParams->eModulation = QAM_32;
					break;
				
				case 64:
					psParams->eModulation = QAM_64;
					break;
				
				case 128:
					psParams->eModulation = QAM_128;
					break;
				
				case 256:
					psParams->eModulation = QAM_256;
					break;
				
				default:
					fprintf (stderr, "\nError: Incorrect modulation value: %s\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}

				psParams->bModulationSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-L") == 0)
			{
				// LNB: Satellite only

				psParams->lLNB = atoi (argv[ii + 1]);

				psParams->bLNBSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-E") == 0)
			{
				_strupr (argv[ii+1]);

				// FEC: Satellite only

				if (strcmp (argv[ii + 1], "1/2") == 0)
				{
					psParams->eFEC = FEC_1_2;
				}
				else if (strcmp (argv[ii + 1], "2/3") == 0)
				{
					psParams->eFEC = FEC_2_3;
				}
				else if (strcmp (argv[ii + 1], "3/4") == 0)
				{
					psParams->eFEC = FEC_3_4;
				}
				else if (strcmp (argv[ii + 1], "5/6") == 0)
				{
					psParams->eFEC = FEC_5_6;
				}
				else if (strcmp (argv[ii + 1], "7/8") == 0)
				{
					psParams->eFEC = FEC_7_8;
				}
				else if (strncmp (argv[ii + 1], "A", 1) == 0)
				{
					psParams->eFEC = FEC_AUTO;
				}
				else
				{
					fprintf (stderr, "\nError: Incorrect value %s for option -e\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}

				psParams->bFECSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-O") == 0)
			{
				_strupr (argv[ii+1]);

				// Polarity: Satellite only

				if (strncmp (argv[ii + 1], "H", 1) == 0)
				{
					psParams->ePolarity = POLARITY_HORIZONTAL;
				}
				else if (strncmp (argv[ii + 1], "V", 1) == 0)
				{
					psParams->ePolarity = POLARITY_VERTICAL;
				}
				else
				{
					fprintf (stderr, "\nError: Incorrect value %s for option -o\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}

				psParams->bPolaritySet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-K") == 0)
			{
				// LNB Selection: Satellite only

				long lLSInput = atoi (argv[ii + 1]);

				if (lLSInput == 0)
				{
					psParams->eLNBSelection = LNB_SELECTION_0;
				}
				else if (lLSInput == 22)
				{
					psParams->eLNBSelection = LNB_SELECTION_22;
				}
				else if (lLSInput == 33)
				{
					psParams->eLNBSelection = LNB_SELECTION_33;
				}
				else if (lLSInput == 44)
				{
					psParams->eLNBSelection = LNB_SELECTION_44;
				}
				else
				{
					fprintf (stderr, "\nError: Incorrect value %s for option -k\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}

				psParams->bLNBSelectionSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-D") == 0)
			{
				_strupr (argv[ii+1]);

				// Diseqc: Satellite only

				if (strncmp (argv[ii + 1], "N", 1) == 0)
				{
					psParams->eDiseqc = DISEQC_NONE;
				}
				else if (strcmp (argv[ii + 1], "A") == 0)
				{
					psParams->eDiseqc = DISEQC_SIMPLE_A;
				}
				else if (strcmp (argv[ii + 1], "B") == 0)
				{
					psParams->eDiseqc = DISEQC_SIMPLE_B;
				}
				else if (strcmp (argv[ii + 1], "A/A") == 0)
				{
					psParams->eDiseqc = DISEQC_LEVEL_1_A_A;
				}
				else if (strcmp (argv[ii + 1], "B/A") == 0)
				{
					psParams->eDiseqc = DISEQC_LEVEL_1_B_A;
				}
				else if (strcmp (argv[ii + 1], "A/B") == 0)
				{
					psParams->eDiseqc = DISEQC_LEVEL_1_A_B;
				}
				else if (strcmp (argv[ii + 1], "B/B") == 0)
				{
					psParams->eDiseqc = DISEQC_LEVEL_1_B_B;
				}
				else
				{
					fprintf (stderr, "\nError: Incorrect value %s for option -d\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}

				psParams->bDiseqcSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-G") == 0)
			{
				_strupr (argv[ii+1]);

				// Guard Interval: Terrestrial DVB Only

				if (strcmp (argv[ii + 1], "1/32") == 0)
				{
					psParams->eGuardInterval = GUARD_INTERVAL_1_32;
				}
				else if (strcmp (argv[ii + 1], "1/16") == 0)
				{
					psParams->eGuardInterval = GUARD_INTERVAL_1_16;
				}
				else if (strcmp (argv[ii + 1], "1/8") == 0)
				{
					psParams->eGuardInterval = GUARD_INTERVAL_1_8;
				}
				else if (strcmp (argv[ii + 1], "1/4") == 0)
				{
					psParams->eGuardInterval = GUARD_INTERVAL_1_4;
				}
				else if (strncmp (argv[ii + 1], "A", 1) == 0)
				{
					psParams->eGuardInterval = GUARD_INTERVAL_AUTO;
				}
				else
				{
					fprintf (stderr, "\nError: Incorrect value %s for option -e\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}

				psParams->bGuardIntervalSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-B") == 0)
			{
				_strupr (argv[ii+1]);

				// Bandwidth: Terrestrial DVB Only

				if (strcmp (argv[ii + 1], "6") == 0)
				{
					psParams->eBandwidth = BANDWIDTH_6_MHZ;
				}
				else if (strcmp (argv[ii + 1], "7") == 0)
				{
					psParams->eBandwidth = BANDWIDTH_7_MHZ;
				}
				else if (strcmp (argv[ii + 1], "8") == 0)
				{
					psParams->eBandwidth = BANDWIDTH_8_MHZ;
				}
				else
				{
					fprintf (stderr, "\nError: Incorrect value %s for option -e\n", argv[ii + 1]);
					fprintf (stderr, USAGE_MSG);

					return -1;	// *** FUNCTION EXIT POINT
				}

				psParams->bBandwidthSet = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-PD") == 0)
			{
				// For backwards compatibility the old PD option still works and will assign
				// each successive PID to successive pins.  In addition the old AddPidMethods 
				// will be used with this call.

				if (AddPidToPinList (psParams, argv[ii + 1],psParams->lTotalPidCount) == -1 )
				{
					return -1;
				}

				psParams->bUseOldAddPidMethods = TRUE;

				ii += 2;
			}
			else if (strcmp (argv[ii], "-P0") == 0)
			{
				if (AddPidToPinList (psParams, argv[ii + 1],DATA_PIN_0) == -1 )
				{
					return -1;
				}

				ii += 2;
			}
			else if (strcmp (argv[ii], "-P1") == 0)
			{
				if (AddPidToPinList (psParams, argv[ii + 1],DATA_PIN_1) == -1 )
				{
					return -1;
				}

				ii += 2;
			}
			else if (strcmp (argv[ii], "-P2") == 0)
			{
				if (AddPidToPinList (psParams, argv[ii + 1],DATA_PIN_2) == -1 )
				{
					return -1;
				}

				ii += 2;
			}
			else if (strcmp (argv[ii], "-P3") == 0)
			{
				if (AddPidToPinList (psParams, argv[ii + 1],DATA_PIN_3) == -1 )
				{
					return -1;
				}

				ii += 2;
			}
			else if (strcmp (argv[ii], "-Y") == 0)
			{
				psParams->bAddDeletePidsSequentially = TRUE;

				ii++;
			}
			else if (strcmp (argv[ii], "-T") == 0)
			{
				// OPTIONAL: Non-tuner specific option

				// Duration (time in seconds) for dump to file

				psParams->lSleepLength = atoi (argv[ii + 1]);

				ii += 2;
			}
			else
			{
				// Catch-all error

				fprintf (stderr, "\nError: Unrecognized option flag %s\n", argv[ii]);
				fprintf (stderr, USAGE_MSG);

				return -1;	// *** FUNCTION EXIT POINT
			}
		}
	}

	return 0;
}


int AddPidToPinList (SParams* psParams, char* pszPid, int iDataPinIndex)
{
	char *		pTerm;
	long		lDataPID;

	// Data PIDs

	if (psParams->lTotalPidCount >= MAX_DATA_PIDS_PER_PIN)
	{
		fprintf (stderr, "\nError: At most 4 Data PIDs may be specified using multiple -pX option\n");
		fprintf (stderr, USAGE_MSG);

		return -1;	// *** FUNCTION EXIT POINT
	}
	else
	{
		if (strlen (pszPid) > 2 &&
			(strncmp (pszPid, "0x", 2) == 0 || strncmp (pszPid, "0X", 2) == 0))
		{
			// convert from hex

			lDataPID = strtoul ((pszPid + 2), &pTerm, 16);
		}
		else
		{
			// assume decimal

			lDataPID = atoi (pszPid);
		}

		// assign to proper PID

		psParams->DataPin[iDataPinIndex].lDataPIDPin[ psParams->DataPin[iDataPinIndex].lPidCount ] = lDataPID;
		psParams->DataPin[iDataPinIndex].lPidCount++;

		psParams->lTotalPidCount++;

		return 0;
	}
}

void CallSysFreeString (BSTR* pbstrFileName)
{
	for (int iDataPinIndex = 0 ; iDataPinIndex < B2C2_FILTER_MAX_TS_PINS ; iDataPinIndex++)
	{
		if (pbstrFileName[iDataPinIndex])
		{
			SysFreeString (pbstrFileName[iDataPinIndex]);
		}
	}

}