/*****************************************************************************
 * DTV_BDA : DirectShow BDA graph for Mpxplay-MMC
 *****************************************************************************
 * Author: Ken Self <kenself(at)optusnet(dot)com(dot)au>
 *         Attila Padar <mpxplay(at)freemail(dot)com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef MPXPLAY_GUI_QT

//#define MPXPLAY_USE_DEBUGF 1
//#define MPXPLAY_USE_WARNINGMSG 1
#include <stdint.h>
#include "newfunc/newfunc.h"
#include "control/cntfuncs.h" // for mpxplay_control_keyboard_get_topfunc()

#define MPXPLAY_DEBUG_OUTPUT NULL // stdout
#define MPXPLAY_DEBUGOUT_SAMPLE NULL // stdout
#define MPXPLAY_DEBUGOUT_READ NULL //stdout
#define MPXPLAY_DEBUGOUT_SAMPLCB NULL // stdout
#define MPXPLAY_DEBUGOUT_FINDFILTER NULL
#define MPXPLAY_DEBUGOUT_RESOURCE stdout
#define MPXPLAY_DEBUGOUT_WARNING NULL // stdout
#define MPXPLAY_DEBUGOUT_ERROR stderr

#if defined(MPXPLAY_DEBUG_OUTPUT) || defined(MPXPLAY_DEBUGOUT_SAMPLE)
#define MPXPLAY_DEBUG_MESSAGE mpxplay_debugf
#else
#define MPXPLAY_DEBUG_MESSAGE(...)
#endif

#include "diskdriv.h"
#include "dtv_bda.hpp"

static const CLSID CLSID_DigitalCableNetworkType = {0x143827AB,0xF77B,0x498d,{0x81,0xCA,0x5A,0x00,0x7A,0xEC,0x28,0xBF}};
static const GUID KSCATEGORY_BDA_NETWORK_TUNER = {0x71985f48,0x1ca1,0x11d3,{0x9c,0xc8,0x00,0xc0,0x4f,0x79,0x71,0xe0}};
static const GUID KSCATEGORY_BDA_RECEIVER_COMPONENT = {0xFD0A5AF4,0xB41D,0x11d2,{0x9c,0x95,0x00,0xc0,0x4f,0x79,0x71,0xe0}};
static const GUID KSDATAFORMAT_SUBTYPE_BDA_MPEG2_TRANSPORT = {0xF4AEB342,0x0329,0x4fdd,{0xA8,0xFD,0x4A,0xFF,0x49,0x26,0xC9,0x78}};
static const GUID KSCATEGORY_BDA_TRANSPORT_INFORMATION = {0xa2e3074f,0x6c3d,0x11d3,{0xb6,0x53,0x00,0xc0,0x4f,0x79,0x49,0x8e}};

static struct dtvresource_chainelem_s *dtv_resource_chain = NULL;

extern const CLSID CLSID_SampleGrabber;
extern const GUID GUID_NULL;

//---------------------------------------------------------------------------------------------

static ModulationType dvb_parse_modulation (const char *mod)
{
    if (!strcmp (mod, "16QAM"))   return BDA_MOD_16QAM;
    if (!strcmp (mod, "32QAM"))   return BDA_MOD_32QAM;
    if (!strcmp (mod, "64QAM"))   return BDA_MOD_64QAM;
    if (!strcmp (mod, "128QAM"))  return BDA_MOD_128QAM;
    if (!strcmp (mod, "256QAM"))  return BDA_MOD_256QAM;
    return BDA_MOD_NOT_SET;
}

static BinaryConvolutionCodeRate dvb_parse_fec (uint32_t fec)
{
    switch (fec)
    {
        case DTVBDA_FEC(1,2): return BDA_BCC_RATE_1_2;
        case DTVBDA_FEC(2,3): return BDA_BCC_RATE_2_3;
        case DTVBDA_FEC(3,4): return BDA_BCC_RATE_3_4;
        case DTVBDA_FEC(5,6): return BDA_BCC_RATE_5_6;
        case DTVBDA_FEC(7,8): return BDA_BCC_RATE_7_8;
    }
    return BDA_BCC_RATE_NOT_SET;
}

static GuardInterval dvb_parse_guard (uint32_t guard)
{
    switch (guard)
    {
        case DTVDBA_GUARD(1, 4): return BDA_GUARD_1_4;
        case DTVDBA_GUARD(1, 8): return BDA_GUARD_1_8;
        case DTVDBA_GUARD(1,16): return BDA_GUARD_1_16;
        case DTVDBA_GUARD(1,32): return BDA_GUARD_1_32;
    }
    return BDA_GUARD_NOT_SET;
}

static TransmissionMode dvb_parse_transmission (int transmit)
{
    switch (transmit)
    {
        case 2: return BDA_XMIT_MODE_2K;
        case 8: return BDA_XMIT_MODE_8K;
    }
    return BDA_XMIT_MODE_NOT_SET;
}

static HierarchyAlpha dvb_parse_hierarchy (int hierarchy)
{
    switch (hierarchy)
    {
        case 1: return BDA_HALPHA_1;
        case 2: return BDA_HALPHA_2;
        case 4: return BDA_HALPHA_4;
    }
    return BDA_HALPHA_NOT_SET;
}

static Polarisation dvb_parse_polarization (char pol)
{
    switch (pol)
    {
        case 'H': return BDA_POLARISATION_LINEAR_H;
        case 'V': return BDA_POLARISATION_LINEAR_V;
        case 'L': return BDA_POLARISATION_CIRCULAR_L;
        case 'R': return BDA_POLARISATION_CIRCULAR_R;
    }
    return BDA_POLARISATION_NOT_SET;
}

static SpectralInversion dvb_parse_inversion (int inversion)
{
    switch (inversion)
    {
        case  0: return BDA_SPECTRAL_INVERSION_NORMAL;
        case  1: return BDA_SPECTRAL_INVERSION_INVERTED;
        case -1: return BDA_SPECTRAL_INVERSION_AUTOMATIC;
    }
    /* should never happen */
    return BDA_SPECTRAL_INVERSION_NOT_SET;
}

/****************************************************************************
 * Interfaces for calls from C
 ****************************************************************************/

dvb_device_t *dtv_bda_device_alloc(void)
{
	dvb_device_t *d = (dvb_device_t *)pds_calloc(1, sizeof(*d));
    if(!d)
    	return d;
    d->frequency = 0;
    d->program_number_id = DTVBDA_DATABUF_BUFTYPE_MULTIPLEX; // give back all programs (as received from device without demultiplexing)
//    d->bandwidth = -1;
    d->srate = 0;
    d->fec = DTVBDA_FEC_AUTO;
    d->fec_hp = DTVBDA_FEC_AUTO;
    d->fec_lp = DTVBDA_FEC_AUTO;
    d->inversion = -1;
    d->lnb_polarization = 0;
    d->lowf = d->highf = d->switchf = 0;
    d->guard = DTVDBA_GUARD_AUTO;
    return d;
}

static int dtv_bda_device_open(dvb_device_t *d)
{
	int retval = MPXPLAY_ERROR_DISKDRIV_ERROR;

	if(!d)
		return retval;
	if(!d->bdagraph_module)
	{
		BDAGraph *module = new BDAGraph();
		if(!module)
			return retval;
		d->bdagraph_module = (void *)module;
	}
	else
	{
		((BDAGraph *)d->bdagraph_module)->StopFilter();
//		((BDAGraph *)d->bdagraph_module)->ClearFilter();
		((BDAGraph *)d->bdagraph_module)->BuffersAllReset(); // FIXME: ???
	}

	retval = MPXPLAY_ERROR_DISKDRIV_OK;

	return retval;
}

static int dtv_bda_device_tune(dvb_device_t *d)
{
	int retval = MPXPLAY_ERROR_DISKDRIV_ERROR;

	if(!d || !d->bdagraph_module)
		return retval;

	switch(d->protocol_number_id)
	{
		case BDADEVTYPE_ATSC:   retval = ((BDAGraph *)d->bdagraph_module)->InitATSCtuner(d); break;
		case BDADEVTYPE_CQAM:   retval = ((BDAGraph *)d->bdagraph_module)->InitCQAMtuner(d); break;
		case BDADEVTYPE_DVB_C:  retval = ((BDAGraph *)d->bdagraph_module)->InitDVBCtuner(d); break;
		case BDADEVTYPE_DVB_T:  retval = ((BDAGraph *)d->bdagraph_module)->InitDVBTtuner(d); break;
		case BDADEVTYPE_DVB_S:
		case BDADEVTYPE_ISDB_S: retval = ((BDAGraph *)d->bdagraph_module)->InitDVBStuner(d); break;
		default: retval = MPXPLAY_ERROR_DISKDRIV_PROTOCOL;
	}

	retval = ((BDAGraph *)d->bdagraph_module)->SetInversion(d, d->inversion);
	if(retval != S_OK)
		return retval;

	retval = ((BDAGraph *)d->bdagraph_module)->SubmitTuneRequest(d);

	return retval;
}

static void dtv_bda_device_close(dvb_device_t *d)
{
	if(d)
	{
		if(d->bdagraph_module)
		{
			delete ((BDAGraph *)d->bdagraph_module);
			d->bdagraph_module = NULL;
		}
	}
}

static int dtv_bda_device_reopen(dvb_device_t *d)
{
	int retval = MPXPLAY_ERROR_DISKDRIV_ERROR;
	if(!d)
		return retval;
//	dtv_bda_device_close(d);   // don't do this !!!
	retval = dtv_bda_device_open(d);
	if(retval == S_OK)
		retval = dtv_bda_device_tune(d);
	return retval;
}

void dtv_bda_device_free(dvb_device_t *d)
{
	if(d){
		pds_free(d);
	}
}

int dtv_bda_device_read(dvb_device_t *d, char *buf, int requested_bytes)
{
	int left_bytes = requested_bytes, got_bytes = 0;

//	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_READ, "dtv_dba_device_read START " );
	if(!d || !d->data_buffer || !buf)
	{
//		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_ERROR, "dtv_bda_device_read invalid args d:%8.8X db:%8.8X rb:%d",
//		  (unsigned int)d, (unsigned int)d->data_buffer, requested_bytes);
		return MPXPLAY_ERROR_DISKDRIV_ERROR;
	}

	if(d->flags & MPXPLAY_DRVDTV_FLAG_READWAIT)
		d->timeout_at_read = 0;

	do{
		int read_bytes;
		if(!d->bdagraph_module || !d->data_buffer || (left_bytes <= 0))
			break;
		read_bytes = ((BDAGraph *)d->bdagraph_module)->BufferRead(d, (uint8_t *)buf, left_bytes);
		if(read_bytes <= 0)
		{
			if(!(d->flags & MPXPLAY_DRVDTV_FLAG_READWAIT))
				break;
			if(d->timeout_at_read == 0)
			{
				//int signal_strength = dtv_bda_device_get_signal_strength(d);
				d->timeout_at_read = pds_gettimem() + MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_READ;
				//MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_READ, "dtv_dba_device_read TIMEOUT START strength:%d %8.8X", signal_strength, signal_strength);
			}
			else if(pds_gettimem() > d->timeout_at_read)
			{
				int signal_strength = dtv_bda_device_get_signal_strength(d);
				int retval = dtv_bda_device_reopen(d);
				d->timeout_at_read = 0;
				got_bytes = MPXPLAY_ERROR_DISKDRIV_TIMEOUT_READ;
				MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_READ, "dtv_dba_device_read TIMEOUT END reopen:%d strength:%d", retval, signal_strength);
				break;
			}
			pds_threads_sleep(50); // !!!
		}
		else
		{
			buf += read_bytes;
			got_bytes += read_bytes;
			left_bytes = requested_bytes - got_bytes;
			d->timeout_at_read = 0;
		}
		if((d->flags & MPXPLAY_DRVDTV_FLAG_TERMINATE) || ((d->flags & MPXPLAY_DRVDTV_FLAG_READWAIT) && !mpxplay_control_keyboard_get_topfunc() && pds_wipeout_by_extkey(KEY_ESC)))
		{
			got_bytes = MPXPLAY_ERROR_DISKDRIV_CONNECT_TERMINATED;
			break;
		}
	}while(got_bytes < requested_bytes);

//	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_READ, "dtv_dba_device_read END b:%d d:%8.8X ss:%d", got_bytes, *((mpxp_uint32_t *)(buf - got_bytes)), dtv_bda_device_get_signal_strength(d));

	return got_bytes;
}

int dtv_bda_device_get_signal_strength(dvb_device_t *d)
{
	if(!d || !d->bdagraph_module)
		return 0;
    return ((BDAGraph *)d->bdagraph_module)->GetSignalStrength( );
}

//------------------------------------------------------------------------------------------
// add a new unique dtv resource (bda graph) to the resource-chain
static struct dtvresource_chainelem_s *dtvdrive_resource_new(dvb_device_t *dtv_device)
{
	struct dtvresource_chainelem_s *res_new = NULL;
	if(!dtv_device)
		return res_new;
	res_new = (struct dtvresource_chainelem_s *)pds_calloc(1, sizeof(struct dtvresource_chainelem_s));
	if(!res_new)
		return res_new;
	if(!dtv_resource_chain)
		dtv_resource_chain = res_new;
	else
	{
		struct dtvresource_chainelem_s *reschainelem = dtv_resource_chain;
		while(reschainelem->next_res)
			reschainelem = reschainelem->next_res;
		reschainelem->next_res = res_new;
		res_new->prev_res = reschainelem;
	}
	pds_memcpy(&res_new->dev_infos, dtv_device, sizeof(res_new->dev_infos));
	return res_new;
}

// Search for the an existent dtv resource (bda graph)
// If found, we can try to use it as shared resource (by adding a new dtvbda_data_buffer to the bda->data_buffers_chain)
// If not found, we can open a new one with this protocol_number and adapter_num
static struct dtvresource_chainelem_s *dtvbda_device_resource_search(dvb_device_t *dtv_device)
{
	struct dtvresource_chainelem_s *reschainelem = dtv_resource_chain;
	if(!dtv_device)
		return NULL;
	while(reschainelem)
	{
		dvb_device_t *res_dev = &reschainelem->dev_infos;
		if((dtv_device->protocol_number_id == res_dev->protocol_number_id) && (dtv_device->adapter_num == res_dev->adapter_num))
			break;
		reschainelem = reschainelem->next_res;
	}
	return reschainelem;
}

// buffer chain has a copy/record element (non-play)
static mpxp_bool_t dtvbda_resource_bufferchain_checkfor_copyelem(dtvbda_data_buffer_s *bdabufs)
{
	mpxp_bool_t found = FALSE;
	do{
		if(!funcbit_test(bdabufs->buffer_flags, DTVBDA_DATABUF_FLAGCTR_OPENMODE_PLAY))
		{
			found = TRUE;
			break;
		}
		bdabufs = bdabufs->next_databuffer;
	}while(bdabufs);
	return found;
}

// search for a specified resource or alloc a new one (bda) tuning the freq
int dtv_bda_resource_assign(dvb_device_t *dtv_device)
{
	struct dtvresource_chainelem_s *reschainelem;
	int retval = MPXPLAY_ERROR_DISKDRIV_ERROR;

	if(!dtv_device)
		return retval;

	reschainelem = dtvbda_device_resource_search(dtv_device);

	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign BEGIN rce:%8.8X dtv:%8.8X omp:%d", (unsigned int)reschainelem, (unsigned int)dtv_device,
			((dtv_device->flags & MPXPLAY_DRVDTV_FLAG_OPENMODE_PLAY)? 1 : 0));
	if(reschainelem)
	{
		dtv_device->dtv_resource = reschainelem;
		dtv_device->bdagraph_module = reschainelem->dev_infos.bdagraph_module;
		if(dtv_device->frequency == reschainelem->dev_infos.frequency)  // TODO: check
		{
			retval = ((BDAGraph *)dtv_device->bdagraph_module)->BufferAdd(dtv_device);
			MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign BufferAdd %d", retval);
			return retval;
		}
		else
		{
			BDAGraph *bdagr = (BDAGraph *)dtv_device->bdagraph_module;
			if(bdagr)
			{
				dtvbda_data_buffer_s *bdabufs = bdagr->data_buffers_chain;
				MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign bdabufs clear %8.8X", (unsigned int)bdabufs);
				if(bdabufs)
				{
					if(dtvbda_resource_bufferchain_checkfor_copyelem(bdabufs) || !(dtv_device->flags & MPXPLAY_DRVDTV_FLAG_OPENMODE_PLAY)) // never terminate copy, terminate play only with other play
					{
						MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_ERROR, "dtv_bda_resource_assign buf ERROR ret:%d nb:%8.8X omb:%d omd:%d", retval, (mpxp_uint32_t)bdabufs->next_databuffer,
						 ((bdabufs->buffer_flags & DTVBDA_DATABUF_FLAGCTR_OPENMODE_PLAY)? 1 : 0), ((dtv_device->flags & MPXPLAY_DRVDTV_FLAG_OPENMODE_PLAY)? 1 : 0));
						return retval;
					}
					MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign CLEAR");
					bdagr->BuffersAllClose();
				}
			}
		}
	}
	else
	{
		reschainelem = dtvdrive_resource_new(dtv_device);
		if(!reschainelem)
			return retval;
		dtv_device->dtv_resource = reschainelem;
	}

	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign dtv_bda_device_open BEGIN");
	retval = dtv_bda_device_open(dtv_device);
	if(retval != MPXPLAY_ERROR_DISKDRIV_OK)
	{
		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign dtv_bda_device_open failed %d", retval);
		goto err_out_assign;
	}

	retval = ((BDAGraph *)dtv_device->bdagraph_module)->BufferAdd(dtv_device);
	if(retval != MPXPLAY_ERROR_DISKDRIV_OK)
	{
		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign BufferAdd failed %d", retval);
		goto err_out_assign;
	}

	retval = dtv_bda_device_tune(dtv_device);
	if(retval != MPXPLAY_ERROR_DISKDRIV_OK)
	{
		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign dtv_bda_device_tune failed %d", retval);
		goto err_out_assign;
	}

	pds_memcpy(&reschainelem->dev_infos, dtv_device, sizeof(reschainelem->dev_infos));

	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign END rce:%8.8X buf:%8.8X", reschainelem, dtv_device->data_buffer);
	return MPXPLAY_ERROR_DISKDRIV_OK;

err_out_assign:
	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_assign err_out_assign");
	dtv_bda_resource_remove(dtv_device);
	return retval;
}

// remove a resource from the chain (if nothing uses it, but this function doesn't check that)
static int dtvbda_device_resource_remove(struct dtvresource_chainelem_s *res_to_remove)
{
	struct dtvresource_chainelem_s *reschainelem = dtv_resource_chain;
	int retval = MPXPLAY_ERROR_DISKDRIV_ERROR;
	if(!res_to_remove)
		return retval;
	while(reschainelem)
	{
		if(reschainelem == res_to_remove) // search for resource to remove from the chain
		{
			if(reschainelem->prev_res)
				reschainelem->prev_res->next_res = reschainelem->next_res; // chain next databuffer to prev one
			if(reschainelem->next_res)
				reschainelem->next_res->prev_res = reschainelem->prev_res; // chain prev databuffer to next one
			if(dtv_resource_chain == res_to_remove)
				dtv_resource_chain = reschainelem->next_res; // replace root elem in the chain with the next one (or set it to NULL if no next one)
			pds_free(res_to_remove);
			retval = MPXPLAY_ERROR_DISKDRIV_OK;
			break;
		}
		reschainelem = reschainelem->next_res;
	}
	return retval;
}

// remove buffer from bda graph, and disalloc resource if it's not used anymore (no entry in the buffer)
int dtv_bda_resource_remove(dvb_device_t *dtv_device)
{
	int retval = MPXPLAY_ERROR_DISKDRIV_ERROR;
	struct dtvresource_chainelem_s *reschainelem;
	BDAGraph *bda_graph;
	if(!dtv_device)
		return retval;
	reschainelem = dtv_device->dtv_resource;
	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "dtv_bda_resource_remove BEGIN rce:%8.8X buf:%8.8X", reschainelem, dtv_device->data_buffer);
	if(!reschainelem)
		return retval;
	bda_graph = (BDAGraph *)dtv_device->bdagraph_module;
	if(bda_graph)
		bda_graph->BufferRemove(dtv_device);
	if(!bda_graph || !bda_graph->data_buffers_chain)
	{
		dtv_bda_device_close(dtv_device);
		retval = dtvbda_device_resource_remove(reschainelem);
	}
	return retval;
}

/*****************************************************************************
* Constructor
*****************************************************************************/
BDAGraph::BDAGraph()
{
	d_graph_register = 0;
	l_tuner_used = -1;
    p_moniker = NULL;
    p_ro_table = NULL;
    media_control_handler = NULL;
    tuning_space_handler = NULL;
    filter_graph_handler = NULL;
    p_system_dev_enum = NULL;
    p_network_provider = p_tuner_device = p_capture_device = NULL;
    p_sample_grabber = p_mpeg_demux = p_transport_info = NULL;
    p_scanning_tuner = NULL;
    p_grabber = NULL;
    ul_cbrc = 0;
    tune_request_handler = NULL;
    dvb_tunerequest_handler = NULL;
    dvbt_locator_handler = NULL;
    samplecb_counter = 0;
    is_discontinuity = FALSE;
    guid_network_type = GUID_NULL;
    data_buffers_chain = NULL;
    pds_memset(&data_buffer_device_read, 0, sizeof(data_buffer_device_read));
    timeout_at_read = 0;
    data_bufferchain_mutex = NULL;
	pds_threads_mutex_new(&data_bufferchain_mutex);
    CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
}

BDAGraph::~BDAGraph()
{
	ClearFilter();
    BuffersAllClose();
    if(data_buffer_device_read.buffer_beginptr)
    	pds_free(data_buffer_device_read.buffer_beginptr);
	pds_threads_mutex_del(&data_bufferchain_mutex);
}

void BDAGraph::ClearFilter()
{
	DestroyFilter();
    if(tuning_space_handler)
    {
        tuning_space_handler->Release();
        tuning_space_handler = NULL;
    }
    CoUninitialize();
}
// -----------------------------------------------------------------------------------------------------------
static void dtvbda_databuffer_reset(struct dtvbda_data_buffer_s *dbuf)
{
	if(dbuf)
	{
		dbuf->buffer_rewindbytes = dbuf->buffer_forwardbytes = dbuf->buffer_getpos = dbuf->buffer_putpos = 0;
//		dbuf->buffer_flags = 0; // FIXME: all?
		funcbit_disable(dbuf->buffer_flags, DTVBDA_DATABUF_FLAGINF_MASK); // TODO: check
	}
}

static void dtvbda_databuffer_close(struct dtvbda_data_buffer_s *dbuf)
{
	if(dbuf)
	{
		dtvbda_databuffer_reset(dbuf);
		if(dbuf->dev_ptr)
			dbuf->dev_ptr->data_buffer = NULL; // TODO: check (because maybe other resource closes all buffers)
		if(dbuf->buffer_beginptr)
		{
			pds_free(dbuf->buffer_beginptr);
			dbuf->buffer_beginptr = NULL;
		}
		pds_free(dbuf);
	}
}

int BDAGraph::BufferAdd(dvb_device_t *d)
{
	struct dtvbda_data_buffer_s *dbuf;
	if(!d)
		return MPXPLAY_ERROR_DISKDRIV_ERROR;
	PDS_THREADS_MUTEX_LOCK(&this->data_bufferchain_mutex, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);
	dbuf = d->data_buffer;
	if(dbuf)
	{
		dtvbda_databuffer_reset(dbuf);
		PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
		return MPXPLAY_ERROR_DISKDRIV_OK;
	}
	d->data_buffer = dbuf = (struct dtvbda_data_buffer_s *)pds_calloc(1, sizeof(struct dtvbda_data_buffer_s));
	if(!dbuf)
		goto err_out_add;
	dbuf->dev_ptr = d;
	if(!this->data_buffers_chain)
		funcbit_enable(dbuf->buffer_flags, DTVBDA_DATABUF_FLAGCTR_READLOCK); // use this flag at the first buffer open only
	if(d->flags & MPXPLAY_DRVDTV_FLAG_OPENMODE_PLAY)
		funcbit_enable(dbuf->buffer_flags, DTVBDA_DATABUF_FLAGCTR_OPENMODE_PLAY);
	dbuf->buffer_type = d->program_number_id;
	dbuf->buffer_size = (dbuf->buffer_type == DTVBDA_DATABUF_BUFTYPE_MULTIPLEX)? DTVBDA_DATABUF_INITSIZE_MUX : DTVBDA_DATABUF_INITSIZE_PROG;
	dbuf->buffer_beginptr = (uint8_t *)pds_malloc(dbuf->buffer_size);
	if(!dbuf->buffer_beginptr)
		goto err_out_add;

	if(!this->data_buffers_chain)
		this->data_buffers_chain = dbuf;
	else
	{
		struct dtvbda_data_buffer_s *dbufchain = this->data_buffers_chain;
		while(dbufchain->next_databuffer)
			dbufchain = dbufchain->next_databuffer;
		dbufchain->next_databuffer = dbuf;
		dbuf->prev_databuffer = dbufchain;
	}
	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);

	return MPXPLAY_ERROR_DISKDRIV_OK;

err_out_add:
	dtvbda_databuffer_close(d->data_buffer);
	d->data_buffer = NULL;
	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
	return MPXPLAY_ERROR_DISKDRIV_ERROR;
}

int BDAGraph::BufferRemove(dvb_device_t *d)
{
	struct dtvbda_data_buffer_s *dbufchain;
	int retVal = MPXPLAY_ERROR_DISKDRIV_ERROR;

	if(!d || !d->data_buffer)
		return retVal;

	PDS_THREADS_MUTEX_LOCK(&this->data_bufferchain_mutex, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);
	dbufchain = this->data_buffers_chain;
	while(dbufchain)
	{
		if(dbufchain == d->data_buffer) // search for data buffer to remove from the chain
		{
			if(dbufchain->next_databuffer)
				dbufchain->next_databuffer->prev_databuffer = dbufchain->prev_databuffer; // chain next databuffer to prev one
			if(dbufchain->prev_databuffer)
				dbufchain->prev_databuffer->next_databuffer = dbufchain->next_databuffer; // chain prev databuffer to next one
			if(this->data_buffers_chain == d->data_buffer)
				this->data_buffers_chain = dbufchain->next_databuffer; // replace root elem in the chain with the next one (or set it to NULL if no next one)
			retVal = MPXPLAY_ERROR_DISKDRIV_OK;
			break;
		}
		dbufchain = dbufchain->next_databuffer;
	}

	dtvbda_databuffer_close(d->data_buffer);
	d->data_buffer = NULL;

	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
	return retVal;
}

void BDAGraph::BuffersAllReadUnlock(void)
{
	struct dtvbda_data_buffer_s *dbufchain;
	PDS_THREADS_MUTEX_LOCK(&this->data_bufferchain_mutex, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);
	dbufchain = this->data_buffers_chain;
	while(dbufchain)
	{
		funcbit_disable(dbufchain->buffer_flags, DTVBDA_DATABUF_FLAGCTR_READLOCK);
		dbufchain = dbufchain->next_databuffer;
	}
	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
}

void BDAGraph::BuffersAllReset(void)
{
	struct dtvbda_data_buffer_s *dbufchain;
	PDS_THREADS_MUTEX_LOCK(&this->data_bufferchain_mutex, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);
	dbufchain = this->data_buffers_chain;
	while(dbufchain)
	{
		dtvbda_databuffer_reset(dbufchain);
		dbufchain = dbufchain->next_databuffer;
	}
	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
}

void BDAGraph::BuffersAllClose(void)
{
	struct dtvbda_data_buffer_s *dbufchain;
	PDS_THREADS_MUTEX_LOCK(&this->data_bufferchain_mutex, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);
	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "BuffersAllClose");
	dbufchain = this->data_buffers_chain;
	while(dbufchain)
	{
		struct dtvbda_data_buffer_s *next;
		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_RESOURCE, "BuffersAllClose %8.8X", (unsigned int)dbufchain);
		next = dbufchain->next_databuffer;
		dtvbda_databuffer_close(dbufchain);
		dbufchain = next;
	}
	this->data_buffers_chain = NULL;
	this->samplecb_counter = 0;
	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
}

static void dtvbda_databuffer_putdata(struct dtvbda_data_buffer_s *dbuf, BYTE *sample_ptr, LONG sample_length)
{
	int left = dbuf->buffer_size - dbuf->buffer_forwardbytes;
	if(sample_length > left)
	{
		if(!funcbit_test(dbuf->buffer_flags, DTVBDA_DATABUF_FLAGINF_ERROR_BUFFER_FULL))
		{
			funcbit_enable(dbuf->buffer_flags, DTVBDA_DATABUF_FLAGINF_ERROR_BUFFER_FULL);
			MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_ERROR, "dtvbda_databuffer_putdata dev:%8.8X buf:%8.8X BUFFER FULL",
					(unsigned int)dbuf->dev_ptr, (unsigned int)dbuf);
		}
		return;
	}
	if(sample_length <= 0)
		return;

	funcbit_disable(dbuf->buffer_flags, DTVBDA_DATABUF_FLAGINF_ERROR_BUFFER_FULL);

	left = dbuf->buffer_size - dbuf->buffer_putpos;
	if(left <= sample_length)
	{
		pds_memcpy(&dbuf->buffer_beginptr[dbuf->buffer_putpos], sample_ptr, left);
		dbuf->buffer_putpos = 0;
		dbuf->buffer_forwardbytes += left;
		sample_length -= left;
		sample_ptr += left;
	}
	if(sample_length > 0)
	{
		pds_memcpy(&dbuf->buffer_beginptr[dbuf->buffer_putpos], sample_ptr, sample_length);
		dbuf->buffer_putpos += sample_length;
		dbuf->buffer_forwardbytes += sample_length;
	}
	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_SAMPLCB, "dtvbda_databuffer_putdata buf:%8.8X fb:%d", (unsigned int)dbuf, dbuf->buffer_forwardbytes);
}

static int dtvbda_databuffer_getdata(struct dtvbda_data_buffer_s *dbuf, uint8_t *destbuf, int requested_bytes)
{
	int left, got_bytes = 0, r_bytes = requested_bytes;
	if(r_bytes > dbuf->buffer_forwardbytes)
		r_bytes = dbuf->buffer_forwardbytes;
	if(r_bytes <= 0)
		return r_bytes;

	left = dbuf->buffer_size - dbuf->buffer_getpos;
	if(left <= r_bytes)
	{
		pds_memcpy(destbuf, &dbuf->buffer_beginptr[dbuf->buffer_getpos], left);
		dbuf->buffer_getpos = 0;
		dbuf->buffer_forwardbytes -= left;
		dbuf->buffer_rewindbytes += left;
		r_bytes -= left;
		destbuf += left;
		got_bytes += left;
	}
	if(r_bytes > 0)
	{
		pds_memcpy(destbuf, &dbuf->buffer_beginptr[dbuf->buffer_getpos], r_bytes);
		dbuf->buffer_getpos += r_bytes;
		dbuf->buffer_forwardbytes -= r_bytes;
		dbuf->buffer_rewindbytes += r_bytes;
		got_bytes += r_bytes;
	}
	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_SAMPLE, "dtvbda_databuffer_getdata buf:%8.8X bf:%7d r:%6d g:%d", (unsigned int)dbuf,
			dbuf->buffer_forwardbytes, requested_bytes, got_bytes);
	return got_bytes;
}

int BDAGraph::BufferRead(dvb_device_t *d, uint8_t *destbuf, int requested_bytes)
{
	int retVal = MPXPLAY_ERROR_DISKDRIV_ERROR;
	if(!d || !d->data_buffer || !destbuf || (requested_bytes <= 0))
	{
		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_ERROR, "BufferRead invalid args rb:%d", requested_bytes);
		return retVal;
	}
	if(PDS_THREADS_MUTEX_LOCK(&this->data_bufferchain_mutex, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX) != MPXPLAY_ERROR_OK)
		return retVal;
	if(funcbit_test(d->data_buffer->buffer_flags, DTVBDA_DATABUF_FLAGCTR_READLOCK))
	{
		retVal = 0;
		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_ERROR, "BufferRead %8.8X %8.8X", (unsigned int)d->data_buffer, d->data_buffer->buffer_flags);
	}
	else
	{
		retVal = dtvbda_databuffer_getdata(d->data_buffer, destbuf, requested_bytes);
	}
	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
	return retVal;
}

// -----------------------------------------------------------------------------------------------------------

int BDAGraph::GetSignalStrength(void)
{
    HRESULT hr = S_OK;
    long sig_strength = 0;
//    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "GetSignalStrength: entering" );
    if( !p_scanning_tuner)
        return -1;
    hr = p_scanning_tuner->get_SignalStrength( &sig_strength );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "GetSignalStrength: Cannot get value: hr=0x%8lx", hr );
        return -1;
    }
//    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "GetSignalStrength: got %ld", l_strength );
//    if(sig_strength & 0xffff0000) // TODO: check
    	sig_strength >>= 16;
//    sig_strength &= 127;
    if(sig_strength > 100)
    	sig_strength = 100;
    return sig_strength;
}

int BDAGraph::SubmitTuneRequest(dvb_device_t *p_dev_cfg)
{
    HRESULT hr;
    int retry = 10;

    do
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SubmitTuneRequest: Building the Graph" );

        hr = Build(p_dev_cfg);
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SubmitTuneRequest: Cannot Build the Graph: hr=0x%8lx", hr );
            return MPXPLAY_ERROR_DISKDRIV_ERROR;
        }
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SubmitTuneRequest: Starting the Graph" );

        hr = StartFilter();
        if( FAILED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SubmitTuneRequest: Cannot Start the Graph, retrying: hr=0x%8lx", hr );
            retry--;
        }
    }
    while((hr != S_OK) && (retry > 0));

    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SubmitTuneRequest: Failed to Start the Graph: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    return MPXPLAY_ERROR_DISKDRIV_OK;
}

/*****************************************************************************
* Init ATSC tuner
*****************************************************************************/
int BDAGraph::InitATSCtuner(dvb_device_t *p_dev_cfg)
{
    HRESULT hr = S_OK;
    class localComPtr
    {
        public:
        ITuneRequest*             p_tune_request;
        IATSCChannelTuneRequest*  p_atsc_tune_request;
        IATSCLocator*             p_atsc_locator;
        localComPtr():
            p_tune_request(NULL),
            p_atsc_tune_request(NULL),
            p_atsc_locator(NULL)
            {};
        ~localComPtr()
        {
            if( p_tune_request )
                p_tune_request->Release();
            if( p_atsc_tune_request )
                p_atsc_tune_request->Release();
            if( p_atsc_locator )
                p_atsc_locator->Release();
        }
    } l;
    long l_frequency = p_dev_cfg->frequency / 1000;
    long l_major_channel = p_dev_cfg->major_channel;
    long l_minor_channel = p_dev_cfg->minor_channel;
    long l_physical_channel = p_dev_cfg->physical_channel;

    hr = Check(p_dev_cfg, CLSID_ATSCNetworkProvider);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot create Tuning Space: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if( !p_scanning_tuner )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot get scanning tuner" );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = p_scanning_tuner->get_TuneRequest( &l.p_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot get Tune Request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_tune_request->QueryInterface( IID_IATSCChannelTuneRequest, reinterpret_cast<void**>( &l.p_atsc_tune_request ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot QI for IATSCChannelTuneRequest: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = ::CoCreateInstance( CLSID_ATSCLocator, 0, CLSCTX_INPROC, IID_IATSCLocator, reinterpret_cast<void**>( &l.p_atsc_locator ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot create the ATSC locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = S_OK;
    if( l_frequency > 0 )
        hr = l.p_atsc_locator->put_CarrierFrequency( l_frequency );
    if( l_major_channel > 0 )
        hr = l.p_atsc_tune_request->put_Channel( l_major_channel );
    if( SUCCEEDED( hr ) && l_minor_channel > 0 )
        hr = l.p_atsc_tune_request->put_MinorChannel( l_minor_channel );
    if( SUCCEEDED( hr ) && l_physical_channel > 0 )
        hr = l.p_atsc_locator->put_PhysicalChannel( l_physical_channel );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot set tuning parameters: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_atsc_tune_request->put_Locator( l.p_atsc_locator );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot put the locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = p_scanning_tuner->Validate( l.p_atsc_tune_request );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitATSC: Tune Request cannot be validated: hr=0x%8lx", hr );
    }

    hr = p_scanning_tuner->put_TuneRequest( l.p_atsc_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitATSC: Cannot put the tune request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    return MPXPLAY_ERROR_DISKDRIV_OK;
}

/*****************************************************************************
* Initialize Clear QAM (DigitalCable) tuner
*****************************************************************************/
int BDAGraph::InitCQAMtuner(dvb_device_t *p_dev_cfg)
{
    HRESULT hr = S_OK;
    class localComPtr
    {
        public:
        ITuneRequest*               p_tune_request;
        IDigitalCableTuneRequest*   p_cqam_tune_request;
        IDigitalCableLocator*       p_cqam_locator;
        localComPtr():
            p_tune_request(NULL),
            p_cqam_tune_request(NULL),
            p_cqam_locator(NULL)
            {};
        ~localComPtr()
        {
            if( p_tune_request )
                p_tune_request->Release();
            if( p_cqam_tune_request )
                p_cqam_tune_request->Release();
            if( p_cqam_locator )
                p_cqam_locator->Release();
        }
    } l;
    long l_frequency = p_dev_cfg->frequency / 1000;
    long l_minor_channel = p_dev_cfg->minor_channel;
    long l_physical_channel = p_dev_cfg->physical_channel;

    hr = Check(p_dev_cfg, CLSID_DigitalCableNetworkType);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot create Tuning Space: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if( !p_scanning_tuner )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot get scanning tuner" );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = p_scanning_tuner->get_TuneRequest( &l.p_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot get Tune Request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_tune_request->QueryInterface( IID_IDigitalCableTuneRequest, reinterpret_cast<void**>( &l.p_cqam_tune_request ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot QI for IDigitalCableTuneRequest: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = ::CoCreateInstance( CLSID_DigitalCableLocator, 0, CLSCTX_INPROC, IID_IDigitalCableLocator, reinterpret_cast<void**>( &l.p_cqam_locator ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot create the CQAM locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = S_OK;
    if( SUCCEEDED( hr ) && l_physical_channel > 0 )
        hr = l.p_cqam_locator->put_PhysicalChannel( l_physical_channel );
    if( SUCCEEDED( hr ) && l_frequency > 0 )
        hr = l.p_cqam_locator->put_CarrierFrequency( l_frequency );
    if( SUCCEEDED( hr ) && l_minor_channel > 0 )
        hr = l.p_cqam_tune_request->put_MinorChannel( l_minor_channel );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot set tuning parameters: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_cqam_tune_request->put_Locator( l.p_cqam_locator );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot put the locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = p_scanning_tuner->Validate( l.p_cqam_tune_request );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitCQAM: Tune Request cannot be validated: hr=0x%8lx", hr );
    }

    hr = p_scanning_tuner->put_TuneRequest( l.p_cqam_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitCQAM: Cannot put the tune request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    return MPXPLAY_ERROR_DISKDRIV_OK;
}

/*****************************************************************************
* Initialize DVB-C tuner
******************************************************************************/
int BDAGraph::InitDVBCtuner(dvb_device_t *p_dev_cfg)
{
    HRESULT hr = S_OK;
    class localComPtr
    {
        public:
        ITuneRequest*       p_tune_request;
        IDVBTuneRequest*    p_dvb_tune_request;
        IDVBCLocator*       p_dvbc_locator;
        IDVBTuningSpace2*   p_dvb_tuning_space;

        localComPtr():
            p_tune_request(NULL),
            p_dvb_tune_request(NULL),
            p_dvbc_locator(NULL),
            p_dvb_tuning_space(NULL)
            {};
        ~localComPtr()
        {
            if( p_tune_request )
                p_tune_request->Release();
            if( p_dvb_tune_request )
                p_dvb_tune_request->Release();
            if( p_dvbc_locator )
                p_dvbc_locator->Release();
            if( p_dvb_tuning_space )
                p_dvb_tuning_space->Release();
        }
    } l;

    long l_frequency = p_dev_cfg->frequency / 1000;
    ModulationType i_qam_mod = dvb_parse_modulation(p_dev_cfg->mod);

    hr = Check(p_dev_cfg, CLSID_DVBCNetworkProvider);
    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: returned from Check" );

    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot create Tuning Space: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: check on scanning tuner" );
    if( !p_scanning_tuner )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot get scanning tuner" );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: get tune request" );
    hr = p_scanning_tuner->get_TuneRequest( &l.p_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot get Tune Request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: QI for dvb tune request" );
    hr = l.p_tune_request->QueryInterface( IID_IDVBTuneRequest, reinterpret_cast<void**>( &l.p_dvb_tune_request ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot QI for IDVBTuneRequest: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    l.p_dvb_tune_request->put_ONID( -1 );
    l.p_dvb_tune_request->put_SID( -1 );
    l.p_dvb_tune_request->put_TSID( -1 );

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: create dvbc locator" );
    hr = ::CoCreateInstance( CLSID_DVBCLocator, 0, CLSCTX_INPROC, IID_IDVBCLocator, reinterpret_cast<void**>( &l.p_dvbc_locator ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot create the DVB-C Locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if(!tuning_space_handler) // TODO: check
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: get TS" );
        hr = p_scanning_tuner->get_TuningSpace( &tuning_space_handler );
        if( FAILED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: cannot get tuning space: hr=0x%8lx", hr );
            return MPXPLAY_ERROR_DISKDRIV_ERROR;
        }
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: QI for dvb tuning space" );
    hr = tuning_space_handler->QueryInterface( IID_IDVBTuningSpace2, reinterpret_cast<void**>( &l.p_dvb_tuning_space ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot QI for IDVBTuningSpace2: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_dvb_tuning_space->put_SystemType( DVB_Cable );
    if( SUCCEEDED( hr ) && l_frequency > 0 )
        hr = l.p_dvbc_locator->put_CarrierFrequency( l_frequency );
    if( SUCCEEDED( hr ) && p_dev_cfg->srate > 0 )
        hr = l.p_dvbc_locator->put_SymbolRate( p_dev_cfg->srate );
    if( SUCCEEDED( hr ) && i_qam_mod != BDA_MOD_NOT_SET )
        hr = l.p_dvbc_locator->put_Modulation( i_qam_mod );

    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot set tuning parameters on Locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: put locator to dvb tune request" );
    hr = l.p_dvb_tune_request->put_Locator( l.p_dvbc_locator );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot put the locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: validate dvb tune request" );
    hr = p_scanning_tuner->Validate( l.p_dvb_tune_request );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: Tune Request cannot be validated: hr=0x%8lx", hr );
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: put dvb tune request to tuner" );
    hr = p_scanning_tuner->put_TuneRequest( l.p_dvb_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBC: Cannot put the tune request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }
    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBC: return success" );

    return MPXPLAY_ERROR_DISKDRIV_OK;
}

/*****************************************************************************
* Initialize DVB-T tuner
******************************************************************************/
int BDAGraph::InitDVBTtuner(dvb_device_t *p_dev_cfg)
{
    HRESULT hr = S_OK;
    long l_frequency = p_dev_cfg->frequency / 1000;
    BinaryConvolutionCodeRate i_hp_fec = dvb_parse_fec(p_dev_cfg->fec_hp);
    BinaryConvolutionCodeRate i_lp_fec = dvb_parse_fec(p_dev_cfg->fec_lp);
    GuardInterval i_guard = dvb_parse_guard(p_dev_cfg->guard);
    TransmissionMode i_transmission = dvb_parse_transmission(p_dev_cfg->transmission);
    HierarchyAlpha i_hierarchy = dvb_parse_hierarchy(p_dev_cfg->hierarchy);

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBT: BEGIN set up scanning tuner freq:%d", l_frequency);
    hr = Check(p_dev_cfg, CLSID_DVBTNetworkProvider);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot create Tuning Space: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if( !p_scanning_tuner )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot get scanning tuner" );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if(!tune_request_handler)
    {
    	hr = p_scanning_tuner->get_TuneRequest( &tune_request_handler );
    	if( FAILED( hr ) )
    	{
    		mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot get Tune Request: hr=0x%8lx", hr );
    		return MPXPLAY_ERROR_DISKDRIV_ERROR;
    	}
    }

    if(!dvb_tunerequest_handler)
    {
    	hr = tune_request_handler->QueryInterface( IID_IDVBTuneRequest, reinterpret_cast<void**>( &dvb_tunerequest_handler ) );
    	if( FAILED( hr ) )
    	{
    		mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot QI for IDVBTuneRequest: hr=0x%8lx", hr );
    		return MPXPLAY_ERROR_DISKDRIV_ERROR;
    	}
    }

    dvb_tunerequest_handler->put_SID( -1 );
    dvb_tunerequest_handler->put_TSID( -1 );
    dvb_tunerequest_handler->put_ONID( -1 );

    if(!dvbt_locator_handler)
    {
    	hr = ::CoCreateInstance(CLSID_DVBTLocator, 0, CLSCTX_INPROC, IID_IDVBTLocator, reinterpret_cast<void**>(&dvbt_locator_handler));
    	if( FAILED( hr ) )
    	{
    		mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot create the DVB-T Locator: hr=0x%8lx", hr );
    		return MPXPLAY_ERROR_DISKDRIV_ERROR;
    	}
    }

    if(!tuning_space_handler)
    {
        hr = p_scanning_tuner->get_TuningSpace( &tuning_space_handler );
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBT: get TS %8.8X", (unsigned int) tuning_space_handler);
        if( FAILED( hr ) )
        {
    	    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBT: cannot get tuning space: hr=0x%8lx", hr );
    	    return MPXPLAY_ERROR_DISKDRIV_ERROR;
        }
    }

    tuning_space_handler->put_DefaultLocator(dvbt_locator_handler);
    tuning_space_handler->put__NetworkType(CLSID_DVBTNetworkProvider);

    if( SUCCEEDED( hr ) && l_frequency > 0 )
        hr = dvbt_locator_handler->put_CarrierFrequency( l_frequency);
    if( SUCCEEDED( hr ) && p_dev_cfg->bandwidth > 0 )
        hr = dvbt_locator_handler->put_Bandwidth( p_dev_cfg->bandwidth );
    if( SUCCEEDED( hr ) && i_hp_fec != BDA_BCC_RATE_NOT_SET )
        hr = dvbt_locator_handler->put_InnerFECRate( i_hp_fec );
    if( SUCCEEDED( hr ) && i_lp_fec != BDA_BCC_RATE_NOT_SET )
        hr = dvbt_locator_handler->put_LPInnerFECRate( i_lp_fec );
    if( SUCCEEDED( hr ) && i_guard != BDA_GUARD_NOT_SET )
        hr = dvbt_locator_handler->put_Guard( i_guard );
    if( SUCCEEDED( hr ) && i_transmission != BDA_XMIT_MODE_NOT_SET )
        hr = dvbt_locator_handler->put_Mode( i_transmission );
    if( SUCCEEDED( hr ) && i_hierarchy != BDA_HALPHA_NOT_SET )
        hr = dvbt_locator_handler->put_HAlpha( i_hierarchy );

    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot set tuning parameters on Locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = dvb_tunerequest_handler->put_Locator(dvbt_locator_handler);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot put the locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = p_scanning_tuner->Validate(dvb_tunerequest_handler);
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Tune Request cannot be validated: hr=0x%8lx", hr );
    }

    hr = p_scanning_tuner->put_TuneRequest(dvb_tunerequest_handler);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Cannot put the tune request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    int retry = 20;
    long currfreq = 0;
    do
    {
    	currfreq = 0;
    	dvbt_locator_handler->get_CarrierFrequency(&currfreq);
    	if(currfreq == l_frequency)
    		break;
    	pds_threads_sleep(100);
    }while(--retry);

    if(currfreq != l_frequency)
    {
    	mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBT: Freq is not tuned! %d vs %d", l_frequency, currfreq);
    	return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBT: END success ss:%d cf:%d", GetSignalStrength(), currfreq);
    return MPXPLAY_ERROR_DISKDRIV_OK;
}

/*****************************************************************************
* Set Inversion
******************************************************************************/
int BDAGraph::SetInversion(dvb_device_t *p_dev_cfg, int inversion)
{
    HRESULT hr = S_OK;
    class localComPtr
    {
        public:
        IDVBSTuningSpace*   p_dvbs_tuning_space;
        localComPtr() :
            p_dvbs_tuning_space(NULL)
        {}
        ~localComPtr()
        {
            if( p_dvbs_tuning_space )
                p_dvbs_tuning_space->Release();
        }
    } l;

    SpectralInversion i_inversion = dvb_parse_inversion( inversion );

    if( !p_scanning_tuner )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetInversion: No scanning tuner" );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if((p_dev_cfg->protocol_number_id & ( BDADEVTYPE_DVB_S | BDADEVTYPE_DVB_S2 | BDADEVTYPE_ISDB_S )) == 0)
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetInversion: Not Satellite type" );
        return MPXPLAY_ERROR_DISKDRIV_OK;
    }

    if(!tuning_space_handler)
    {
        hr = p_scanning_tuner->get_TuningSpace( &tuning_space_handler );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetInversion: cannot get tuning space: hr=0x%8lx", hr );
            return MPXPLAY_ERROR_DISKDRIV_ERROR;
        }
    }

    hr = tuning_space_handler->QueryInterface( IID_IDVBSTuningSpace, reinterpret_cast<void**>( &l.p_dvbs_tuning_space ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetInversion: Cannot QI for IDVBSTuningSpace: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if( i_inversion != BDA_SPECTRAL_INVERSION_NOT_SET )
    {
        hr = l.p_dvbs_tuning_space->put_SpectralInversion( i_inversion );
        if( FAILED( hr ) )
        {
        	mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetInversion: Cannot put inversion: hr=0x%8lx", hr );
        	return MPXPLAY_ERROR_DISKDRIV_ERROR;
        }
    }

    return MPXPLAY_ERROR_DISKDRIV_OK;
}

/*****************************************************************************
* Set DVB-S
******************************************************************************/
int BDAGraph::InitDVBStuner(dvb_device_t *p_dev_cfg)
{
    HRESULT hr = S_OK;
    class localComPtr
    {
        public:
        ITuneRequest*       p_tune_request;
        IDVBTuneRequest*    p_dvb_tune_request;
        IDVBSLocator*       p_dvbs_locator;
        IDVBSTuningSpace*   p_dvbs_tuning_space;
        char*               psz_polarisation;
        BSTR                bstr_input_range;
        WCHAR*              pwsz_input_range;
        int                 i_range_len;
        localComPtr() :
            p_tune_request(NULL),
            p_dvb_tune_request(NULL),
            p_dvbs_locator(NULL),
            p_dvbs_tuning_space(NULL),
            psz_polarisation(NULL),
            bstr_input_range(NULL),
            pwsz_input_range(NULL),
            i_range_len(0)
        {}
        ~localComPtr()
        {
            if( p_tune_request )
                p_tune_request->Release();
            if( p_dvb_tune_request )
                p_dvb_tune_request->Release();
            if( p_dvbs_locator )
                p_dvbs_locator->Release();
            if( p_dvbs_tuning_space )
                p_dvbs_tuning_space->Release();
            SysFreeString( bstr_input_range );
            if( pwsz_input_range )
                delete[] pwsz_input_range;
            free( psz_polarisation );
        }
    } l;
    long l_frequency = p_dev_cfg->frequency / 1000;
    VARIANT_BOOL b_west;

    BinaryConvolutionCodeRate i_hp_fec = dvb_parse_fec( p_dev_cfg->fec );
    Polarisation i_polar = dvb_parse_polarization( p_dev_cfg->lnb_polarization );
    SpectralInversion i_inversion = dvb_parse_inversion( p_dev_cfg->inversion );

    b_west = ( p_dev_cfg->longitude < 0 );

    if(p_dev_cfg->range)
    {
    	l.i_range_len = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p_dev_cfg->range, -1, l.pwsz_input_range, 0 );
    	if( l.i_range_len > 0 )
    	{
    		l.pwsz_input_range = new WCHAR[l.i_range_len];
    		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p_dev_cfg->range, -1, l.pwsz_input_range, l.i_range_len );
    		l.bstr_input_range = SysAllocString( l.pwsz_input_range );
    	}
    }

    hr = Check(p_dev_cfg, CLSID_DVBSNetworkProvider);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot create Tuning Space: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if( !p_scanning_tuner )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot get scanning tuner" );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = p_scanning_tuner->get_TuneRequest( &l.p_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot get Tune Request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_tune_request->QueryInterface( IID_IDVBTuneRequest, reinterpret_cast<void**>( &l.p_dvb_tune_request ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot QI for IDVBTuneRequest: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    l.p_dvb_tune_request->put_ONID( -1 );
    l.p_dvb_tune_request->put_SID( -1 );
    l.p_dvb_tune_request->put_TSID( -1 );

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBS: create dvbc locator" );
    hr = ::CoCreateInstance( CLSID_DVBSLocator, 0, CLSCTX_INPROC, IID_IDVBSLocator, reinterpret_cast<void**>( &l.p_dvbs_locator ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot create the DVBS Locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    if(!tuning_space_handler) // TODO: check
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBS: get TS" );
        hr = p_scanning_tuner->get_TuningSpace( &tuning_space_handler );
        if( FAILED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBS: cannot get tuning space: hr=0x%8lx", hr );
            return MPXPLAY_ERROR_DISKDRIV_ERROR;
        }
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBS: QI for dvb tuning space" );
    hr = tuning_space_handler->QueryInterface( IID_IDVBSTuningSpace, reinterpret_cast<void**>( &l.p_dvbs_tuning_space ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot QI for IDVBSTuningSpace: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_dvbs_tuning_space->put_SystemType( DVB_Satellite );
    if( SUCCEEDED( hr ) && p_dev_cfg->lowf > 0 )
        hr = l.p_dvbs_tuning_space->put_LowOscillator( p_dev_cfg->lowf );
    if( SUCCEEDED( hr ) && p_dev_cfg->switchf > 0 )
        hr = l.p_dvbs_tuning_space->put_LNBSwitch( p_dev_cfg->switchf );
    if( SUCCEEDED( hr ) && p_dev_cfg->highf > 0 )
        hr = l.p_dvbs_tuning_space->put_HighOscillator( p_dev_cfg->highf );
    if( SUCCEEDED( hr ) && i_inversion != BDA_SPECTRAL_INVERSION_NOT_SET )
        hr = l.p_dvbs_tuning_space->put_SpectralInversion( i_inversion );
    if( SUCCEEDED( hr ) && p_dev_cfg->network_id > 0 )
        hr = l.p_dvbs_tuning_space->put_NetworkID( p_dev_cfg->network_id );
    if( SUCCEEDED( hr ) && l.i_range_len > 0 )
        hr = l.p_dvbs_tuning_space->put_InputRange( l.bstr_input_range );

    if( SUCCEEDED( hr ) && l_frequency > 0 )
        hr = l.p_dvbs_locator->put_CarrierFrequency( l_frequency );
    if( SUCCEEDED( hr ) && p_dev_cfg->srate > 0 )
        hr = l.p_dvbs_locator->put_SymbolRate( p_dev_cfg->srate );
    if( SUCCEEDED( hr ) && i_polar != BDA_POLARISATION_NOT_SET )
        hr = l.p_dvbs_locator->put_SignalPolarisation( i_polar );
    if( SUCCEEDED( hr ) )
        hr = l.p_dvbs_locator->put_Modulation( BDA_MOD_QPSK );
    if( SUCCEEDED( hr ) && i_hp_fec != BDA_BCC_RATE_NOT_SET )
        hr = l.p_dvbs_locator->put_InnerFECRate( i_hp_fec );

    if( SUCCEEDED( hr ) && p_dev_cfg->azimuth > 0 )
        hr = l.p_dvbs_locator->put_Azimuth( p_dev_cfg->azimuth );
    if( SUCCEEDED( hr ) && p_dev_cfg->elevation > 0 )
        hr = l.p_dvbs_locator->put_Elevation( p_dev_cfg->elevation );
    if( SUCCEEDED( hr ) )
        hr = l.p_dvbs_locator->put_WestPosition( b_west );
    if( SUCCEEDED( hr ) )
        hr = l.p_dvbs_locator->put_OrbitalPosition( labs( p_dev_cfg->longitude ) );

    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot set tuning parameters on Locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = l.p_dvb_tune_request->put_Locator( l.p_dvbs_locator );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot put the locator: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    hr = p_scanning_tuner->Validate( l.p_dvb_tune_request );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "InitDVBS: Tune Request cannot be validated: hr=0x%8lx", hr );
    }

    /* increments ref count for scanning tuner */
    hr = p_scanning_tuner->put_TuneRequest( l.p_dvb_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "InitDVBS: Cannot put the tune request: hr=0x%8lx", hr );
        return MPXPLAY_ERROR_DISKDRIV_ERROR;
    }

    return MPXPLAY_ERROR_DISKDRIV_OK;
}

/*****************************************************************************
* SetUpTuner
******************************************************************************
* Sets up global p_scanning_tuner and sets guid_network_type according
* to the Network Type requested.
*
* Logic: if tuner is set up and is the right network type, use it.
* Otherwise, poll the tuner for the right tuning space. 
*
* Then set up a tune request and try to validate it. Finally, put
* tune request and tuning space to tuner
*
* on success, sets globals: p_scanning_tuner and guid_network_type
*
******************************************************************************/
HRESULT BDAGraph::SetUpTuner(dvb_device_t *p_dev_cfg, REFCLSID guid_this_network_type )
{
    HRESULT hr = S_OK;
    class localComPtr
    {
        public:
        IEnumTuningSpaces*          p_tuning_space_enum;
        ITuningSpace*               p_test_tuning_space;
        ITuneRequest*               p_tune_request;
        IDVBTuneRequest*            p_dvb_tune_request;

        IDigitalCableTuneRequest*   p_cqam_tune_request;
        IATSCChannelTuneRequest*    p_atsc_tune_request;
        ILocator*                   p_locator;
        IDVBTLocator*               p_dvbt_locator;
        IDVBCLocator*               p_dvbc_locator;
        IDVBSLocator*               p_dvbs_locator;

        BSTR                        bstr_name;

        CLSID                       guid_test_network_type;
        char*                       psz_bstr_name;
        int                         i_name_len;

        localComPtr():
            p_tuning_space_enum(NULL),
            p_test_tuning_space(NULL),
            p_tune_request(NULL),
            p_dvb_tune_request(NULL),
            p_cqam_tune_request(NULL),
            p_atsc_tune_request(NULL),
            p_locator(NULL),
            p_dvbt_locator(NULL),
            p_dvbc_locator(NULL),
            p_dvbs_locator(NULL),
            bstr_name(NULL),
            guid_test_network_type(GUID_NULL),
            psz_bstr_name(NULL),
            i_name_len(0)
        {}
        ~localComPtr()
        {
            if( p_tuning_space_enum )
                p_tuning_space_enum->Release();
            if( p_test_tuning_space )
                p_test_tuning_space->Release();
            if( p_tune_request )
                p_tune_request->Release();
            if( p_dvb_tune_request )
                p_dvb_tune_request->Release();
            if( p_cqam_tune_request )
                p_cqam_tune_request->Release();
            if( p_atsc_tune_request )
                p_atsc_tune_request->Release();
            if( p_locator )
                p_locator->Release();
            if( p_dvbt_locator )
                p_dvbt_locator->Release();
            if( p_dvbc_locator )
                p_dvbc_locator->Release();
            if( p_dvbs_locator )
                p_dvbs_locator->Release();
            SysFreeString( bstr_name );
            delete[] psz_bstr_name;
        }
    } l;

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner BEGIN" );


    /* We shall test for a specific Tuning space name supplied on the command
     * line as dvb-network-name=nnn.
     * For some users with multiple cards and/or multiple networks this could
     * be useful. This allows us to reasonably safely apply updates to the
     * System Tuning Space in the registry without disrupting other streams. */

    if( p_dev_cfg->network_name )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Find Tuning Space: %s", p_dev_cfg->network_name );
    }

    /* Tuner may already have been set up. If it is for the same
     * network type then all is well. Otherwise, reset the Tuning Space and get
     * a new one */
    //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Checking for tuning space" );
    if( !p_scanning_tuner )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetUpTuner: Cannot find scanning tuner" );
        return E_FAIL;
    }

    if( tuning_space_handler )
    {
        //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: get network type" );
        hr = tuning_space_handler->get__NetworkType( &l.guid_test_network_type );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetUpTuner: Cannot get network type: hr=0x%8lx", hr );
            l.guid_test_network_type = GUID_NULL;
        }

        //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: see if it's the right one" );
        if( l.guid_test_network_type == guid_this_network_type )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: it's the right one" );
            SysFreeString( l.bstr_name );

            hr = tuning_space_handler->get_UniqueName( &l.bstr_name );
            if( FAILED( hr ) )
            {
                /* should never fail on a good tuning space */
                MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Cannot get UniqueName for Tuning Space: hr=0x%8lx", hr );
                goto NoTuningSpace;
            }

            /* Test for a specific Tuning space name supplied on the command line as dvb-network-name=nnn */
            if( l.psz_bstr_name )
                delete[] l.psz_bstr_name;
            l.i_name_len = WideCharToMultiByte( CP_ACP, 0, l.bstr_name, -1, l.psz_bstr_name, 0, NULL, NULL );
            l.psz_bstr_name = new char[ l.i_name_len ];
            l.i_name_len = WideCharToMultiByte( CP_ACP, 0, l.bstr_name, -1, l.psz_bstr_name, l.i_name_len, NULL, NULL );

            /* if no name was requested on command line, or if the name
             * requested equals the name of this space, we are OK */
            if(!p_dev_cfg->network_name || (strcmp( p_dev_cfg->network_name, l.psz_bstr_name ) == 0) )
            {
                //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Using Tuning Space: %s", l.psz_bstr_name );
                /* tuning_space_handler and guid_network_type are already set */
                /* you probably already have a tune request, also */
                hr = p_scanning_tuner->get_TuneRequest( &l.p_tune_request );
                if( SUCCEEDED( hr ) )
                {
                    return S_OK;
                }
                /* CreateTuneRequest adds l.p_tune_request to tuning_space_handler
                 * l.p_tune_request->RefCount = 1 */
                hr = tuning_space_handler->CreateTuneRequest( &l.p_tune_request );
                if( SUCCEEDED( hr ) )
                {
                    return S_OK;
                }
                mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetUpTuner: Cannot Create Tune Request: hr=0x%8lx", hr );
               /* fall through to NoTuningSpace */
            }
        }

        /* else different guid_network_type */
NoTuningSpace:
        if(tuning_space_handler)
        {
            tuning_space_handler->Release();
            tuning_space_handler = NULL;
        }
    }

    //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: find a tuning space on the scanning tuner (release TuningSpaces Enumerator)" );
    if(l.p_tuning_space_enum)
    {
        l.p_tuning_space_enum->Release();
        l.p_tuning_space_enum = NULL;
    }

    //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: create TuningSpaces Enumerator" );
    hr = p_scanning_tuner->EnumTuningSpaces( &l.p_tuning_space_enum );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetUpTuner: Cannot create TuningSpaces Enumerator: hr=0x%8lx", hr );
        goto TryToClone;
    }

    do
    {
        //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: top of loop" );
        l.guid_test_network_type = GUID_NULL;
        if(l.p_test_tuning_space)
        {
            l.p_test_tuning_space->Release();
            l.p_test_tuning_space = NULL;
        }
        SysFreeString( l.bstr_name );
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: need good TS enum" );
        if( !l.p_tuning_space_enum )
        	break;
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: next tuning space" );
        hr = l.p_tuning_space_enum->Next( 1, &l.p_test_tuning_space, NULL );
        if( hr != S_OK )
        	break;
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: get network type" );
        hr = l.p_test_tuning_space->get__NetworkType( &l.guid_test_network_type );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Check: Cannot get network type: hr=0x%8lx", hr );
            l.guid_test_network_type = GUID_NULL;
        }
        if( l.guid_test_network_type == guid_this_network_type )
        {
            //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Found matching space on tuner" );
            SysFreeString( l.bstr_name );
            //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: get unique name" );
            hr = l.p_test_tuning_space->get_UniqueName( &l.bstr_name );
            if( FAILED( hr ) )
            {
                /* should never fail on a good tuning space */
                MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Cannot get UniqueName for Tuning Space: hr=0x%8lx", hr );
                continue;
            }
            //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: convert w to multi" );
            if ( l.psz_bstr_name )
                delete[] l.psz_bstr_name;
            l.i_name_len = WideCharToMultiByte( CP_ACP, 0, l.bstr_name, -1, l.psz_bstr_name, 0, NULL, NULL );
            l.psz_bstr_name = new char[ l.i_name_len ];
            l.i_name_len = WideCharToMultiByte( CP_ACP, 0, l.bstr_name, -1, l.psz_bstr_name, l.i_name_len, NULL, NULL );
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Using Tuning Space: %s", l.psz_bstr_name );
            break;
        }

    }
    while( true );

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: checking what we got" );

    if( l.guid_test_network_type == GUID_NULL)
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: got null network type, try to clone" );
        goto TryToClone;
    }

    hr = p_scanning_tuner->put_TuningSpace( l.p_test_tuning_space );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: cannot put tuning space: hr=0x%8lx", hr );
        goto TryToClone;
    }

    hr = l.p_test_tuning_space->get_DefaultLocator( &l.p_locator );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: cannot get default locator: hr=0x%8lx", hr );
        goto TryToClone;
    }

    hr = l.p_test_tuning_space->CreateTuneRequest( &l.p_tune_request );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: cannot create tune request: hr=0x%8lx", hr );
        goto TryToClone;
    }

    hr = l.p_tune_request->put_Locator( l.p_locator );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: cannot put locator: hr=0x%8lx", hr );
        goto TryToClone;
    }

    hr = p_scanning_tuner->Validate( l.p_tune_request );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner: Tune Request cannot be validated: hr=0x%8lx", hr );
    }

    /* increments ref count for scanning tuner */
    hr = p_scanning_tuner->put_TuneRequest( l.p_tune_request );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetUpTuner: Cannot submit the tune request: hr=0x%8lx", hr );
        return hr;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "SetUpTuner END: Tuning Space and Tune Request created" );
    return S_OK;

TryToClone:
    mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "SetUpTuner: won't try to clone " );
    return E_FAIL;
}

/******************************************************************************
* Check if tuner supports this network type
******************************************************************************/
HRESULT BDAGraph::Check(dvb_device_t *p_dev_cfg, REFCLSID guid_this_network_type )
{
    HRESULT hr;

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Check: BEGIN");

    if(guid_network_type == guid_this_network_type)  // FIXME
    {
    	return S_OK;
    }

    DestroyFilter();

    hr = ::CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, reinterpret_cast<void**>( &filter_graph_handler ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Check: Cannot CoCreate IFilterGraph: hr=0x%8lx", hr );
        return hr;
    }

    hr = ::CoCreateInstance( guid_this_network_type, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>( &p_network_provider ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Check: Cannot CoCreate Network Provider: hr=0x%8lx", hr );
        return hr;
    }

    hr = filter_graph_handler->AddFilter(p_network_provider, NULL);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Check: Cannot add network provider to graph: hr=0x%8lx", hr );
        return hr;
    }

    if((l_tuner_used < 0) && (p_dev_cfg->adapter_num >= 0))
    {
        l_tuner_used = p_dev_cfg->adapter_num - 1;
    }

    hr = p_network_provider->QueryInterface( IID_IScanningTuner, reinterpret_cast<void**>( &p_scanning_tuner ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Check: Cannot QI Network Provider for Scanning Tuner: hr=0x%8lx", hr );
        return hr;
    }

    hr = SetUpTuner(p_dev_cfg, guid_this_network_type );
    if( FAILED( hr ) )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Check: Cannot set up scanner in Check mode: hr=0x%8lx", hr );
        return hr;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Check: Calling FindFilter for KSCATEGORY_BDA_NETWORK_TUNER with p_network_provider; l_tuner_used=%ld", l_tuner_used );
    hr = FindFilter( KSCATEGORY_BDA_NETWORK_TUNER, &l_tuner_used, p_network_provider, &p_tuner_device );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Check: Cannot load tuner device and connect network provider (FindFilter failed): hr=0x%8lx", hr );
        return hr;
    }

    if((p_dev_cfg->adapter_num > 0) && (l_tuner_used != p_dev_cfg->adapter_num))
    {
         mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Check: Tuner device %ld is not available", p_dev_cfg->adapter_num);
         return E_FAIL;
    }

    guid_network_type = guid_this_network_type;

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Check: END OK Using adapter %ld", l_tuner_used );
    return S_OK;
}

/******************************************************************************
* Build the Filter Graph
* connects filters and creates the media control and registers the graph
******************************************************************************/
HRESULT BDAGraph::Build(dvb_device_t *p_dev_cfg)
{
    HRESULT hr = S_OK;
    long l_capture_used, l_tif_used;
    AM_MEDIA_TYPE grabber_media_type;

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: entering");

    if(media_control_handler)  // FIXME
    {
    	return S_OK;
    }

    if( !p_scanning_tuner || !p_tuner_device )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Scanning Tuner does not exist" );
        return E_FAIL;
    }

    if(media_control_handler)
    {
        media_control_handler->Release();
        media_control_handler = NULL;
    }
    if( p_mpeg_demux )
    {
        p_mpeg_demux->Release();
        p_mpeg_demux = NULL;
    }
    if( p_grabber )
    {
         p_grabber->Release();
         p_grabber = NULL;
    }
    if( p_sample_grabber )
    {
         p_sample_grabber->Release();
         p_sample_grabber = NULL;
    }

    if(!tune_request_handler)
    {
        hr = p_scanning_tuner->get_TuneRequest( &tune_request_handler );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: no tune request" );
            return hr;
        }
    }
    if(!tuning_space_handler)
    {
        hr = p_scanning_tuner->get_TuningSpace( &tuning_space_handler );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: no tuning space" );
            return hr;
        }
    }

    l_capture_used = -1;
    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: Calling FindFilter for KSCATEGORY_BDA_RECEIVER_COMPONENT with p_tuner_device; l_capture_used=%ld", l_capture_used );
    hr = FindFilter( KSCATEGORY_BDA_RECEIVER_COMPONENT, &l_capture_used, p_tuner_device, &p_capture_device );
    if( FAILED( hr ) )
    {
        /* Some BDA drivers do not provide a Capture Device Filter so force the Sample Grabber to connect directly to the Tuner Device */
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: Cannot find Capture device. Connect to tuner and AddRef() : hr=0x%8lx", hr );
        p_capture_device = p_tuner_device;
        p_capture_device->AddRef();
    }

    hr = ::CoCreateInstance( CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>( &p_sample_grabber ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot load Sample Grabber Filter: hr=0x%8lx", hr );
        return hr;
    }

    hr = filter_graph_handler->AddFilter( p_sample_grabber, NULL);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot add Sample Grabber Filter to graph: hr=0x%8lx", hr );
        return hr;
    }

    hr = p_sample_grabber->QueryInterface( IID_ISampleGrabber, reinterpret_cast<void**>( &p_grabber ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot QI Sample Grabber Filter: hr=0x%8lx", hr );
        return hr;
    }

    hr = E_FAIL;
    for( int i = 0; i < 2; i++ )
    {
        ZeroMemory( &grabber_media_type, sizeof( AM_MEDIA_TYPE ) );
        grabber_media_type.majortype = MEDIATYPE_Stream;
//        grabber_media_type.subtype = (i == 0)? MEDIASUBTYPE_MPEG2_TRANSPORT : KSDATAFORMAT_SUBTYPE_BDA_MPEG2_TRANSPORT;
        grabber_media_type.subtype = (i == 0)? KSDATAFORMAT_SUBTYPE_BDA_MPEG2_TRANSPORT : MEDIASUBTYPE_MPEG2_TRANSPORT;
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: Trying connecting with subtype %s",
                  (i == 0)? "KSDATAFORMAT_SUBTYPE_BDA_MPEG2_TRANSPORT" : "MEDIASUBTYPE_MPEG2_TRANSPORT" );
        hr = p_grabber->SetMediaType( &grabber_media_type );
        if( SUCCEEDED( hr ) )
        {
            hr = ConnectFilterPins( p_capture_device, p_sample_grabber );
            if( SUCCEEDED( hr ) )
            {
                MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: Connected capture device to sample grabber" );
                break;
            }
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot connect Sample Grabber to Capture device: hr=0x%8lx (try %d/2)", hr, 1+i );
        }
        else
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot set media type on grabber filter: hr=0x%8lx (try %d/2", hr, 1+i );
        }
    }

    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot use capture device: hr=0x%8lx", hr );
        return hr;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: using MPEG2 demux" );
    hr = ::CoCreateInstance( CLSID_MPEG2Demultiplexer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>( &p_mpeg_demux ) );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot CoCreateInstance MPEG2 Demultiplexer: hr=0x%8lx", hr );
        return hr;
    }

    hr = filter_graph_handler->AddFilter( p_mpeg_demux, NULL);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot add demux filter to graph: hr=0x%8lx", hr );
        return hr;
    }

    hr = ConnectFilterPins( p_sample_grabber, p_mpeg_demux );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot connect demux to grabber: hr=0x%8lx", hr );
        return hr;
    }

    l_tif_used = -1;
    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Check: Calling FindFilter for KSCATEGORY_BDA_TRANSPORT_INFORMATION with p_mpeg_demux; l_tif_used=%ld", l_tif_used );
    hr = FindFilter( KSCATEGORY_BDA_TRANSPORT_INFORMATION, &l_tif_used, p_mpeg_demux, &p_transport_info );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot load TIF onto demux: hr=0x%8lx", hr );
        return hr;
    }

    hr = p_grabber->SetBufferSamples(false);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot set Sample Grabber to buffering: hr=0x%8lx", hr );
        return hr;
    }

    hr = p_grabber->SetOneShot(false);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot set Sample Grabber to multi shot: hr=0x%8lx", hr );
        return hr;
    }

    hr = p_grabber->SetCallback(this, 0);
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot set SampleGrabber Callback: hr=0x%8lx", hr );
        return hr;
    }

    hr = RegisterFilter();
    if(FAILED(hr))
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: Cannot register graph: hr=0x%8lx", hr );
        return hr;
    }

    hr = filter_graph_handler->QueryInterface( IID_IMediaControl, reinterpret_cast<void**>(&media_control_handler));
    if(FAILED(hr))
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Build: Cannot QI Media Control: hr=0x%8lx", hr );
        return hr;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Build: succeeded: hr=0x%8lx", hr );
    return S_OK;
}

/******************************************************************************
* FindFilter
* Looks up all filters in a category and connects to the upstream filter until
* a successful match is found. The index of the connected filter is returned.
* On subsequent calls, this can be used to start from that point to find
* another match.
* This is used when the graph does not run because a tuner device is in use so
* another one needs to be selected.
******************************************************************************/
HRESULT BDAGraph::FindFilter( REFCLSID this_clsid, long* i_moniker_used, IBaseFilter* p_upstream, IBaseFilter** p_p_downstream )
{
    HRESULT                 hr = S_OK;
    int                     i_moniker_index = -1;
    class localComPtr
    {
        public:
        IEnumMoniker*  p_moniker_enum;
        IMoniker*      p_moniker;
        IBindCtx*      p_bind_context;
        IPropertyBag*  p_property_bag;
        char*          psz_upstream;
        VARIANT        var_bstr;
        localComPtr():
            p_moniker_enum(NULL),
            p_moniker(NULL),
            p_bind_context( NULL ),
            p_property_bag(NULL),
            psz_upstream( NULL )
        {
            ::VariantInit(&var_bstr);
        }
        ~localComPtr()
        {
            if( p_moniker_enum )
                p_moniker_enum->Release();
            if( p_moniker )
                p_moniker->Release();
            if( p_bind_context )
                p_bind_context->Release();
            if( p_property_bag )
                p_property_bag->Release();
            if( psz_upstream )
                delete[] psz_upstream;
            ::VariantClear(&var_bstr);
        }
    } l;

    if( !p_system_dev_enum )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Create p_system_dev_enum");
        hr = ::CoCreateInstance( CLSID_SystemDeviceEnum, 0, CLSCTX_INPROC, IID_ICreateDevEnum, reinterpret_cast<void**>( &p_system_dev_enum ) );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "FindFilter: Cannot CoCreate SystemDeviceEnum: hr=0x%8lx", hr );
            return hr;
        }
    }

//    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Create p_moniker_enum");
    hr = p_system_dev_enum->CreateClassEnumerator( this_clsid, &l.p_moniker_enum, 0 );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "FindFilter: Cannot CreateClassEnumerator: hr=0x%8lx", hr );
        return hr;
    }

//    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: get filter name");
    hr = GetFilterName( p_upstream, &l.psz_upstream );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "FindFilter: Cannot GetFilterName: hr=0x%8lx", hr );
        return hr;
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: called with i_moniker_used=%ld, p_upstream = %s", *i_moniker_used, l.psz_upstream );

    do
    {
//        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_FINDFILTER, "FindFilter: top of main loop");
        if( l.p_property_bag )
        {
            l.p_property_bag->Release();
            l.p_property_bag = NULL;
        }
//        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_FINDFILTER, "FindFilter: releasing bind context");
        if( l.p_bind_context )
        {
           l.p_bind_context->Release();
           l.p_bind_context = NULL;
        }
//        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_FINDFILTER, "FindFilter: releasing moniker");
        if( l.p_moniker )
        {
            l.p_moniker->Release();
            l.p_moniker = NULL;
        }

        if(!l.p_moniker_enum )
        {
        	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: quit because no enum");
        	break;
        }

        hr = l.p_moniker_enum->Next( 1, &l.p_moniker, 0 );
        if( hr != S_OK )
        {
        	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: trying a moniker failed");
        	break;
        }

        i_moniker_index++;

 //       MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_FINDFILTER, "FindFilter: skip previously found devices");
        if( i_moniker_index <= *i_moniker_used )
        	continue;
        *i_moniker_used = i_moniker_index;

        hr = CreateBindCtx( 0, &l.p_bind_context );
        if( FAILED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Cannot create bind_context, trying another: hr=0x%8lx", hr );
            continue;
        }

        *p_p_downstream = NULL;
        hr = l.p_moniker->BindToObject( l.p_bind_context, NULL, IID_IBaseFilter, reinterpret_cast<void**>( p_p_downstream ) );
        if( FAILED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Cannot bind to downstream, trying another: hr=0x%8lx", hr );
            continue;
        }

        hr = l.p_moniker->BindToStorage( l.p_bind_context, NULL, IID_IPropertyBag, reinterpret_cast<void**>( &l.p_property_bag ) );
        if( FAILED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Cannot Bind to Property Bag: hr=0x%8lx", hr );
            continue;
        }

        hr = l.p_property_bag->Read( L"FriendlyName", &l.var_bstr, NULL );
        if( FAILED( hr ) )
        {
           MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Cannot read friendly name, next?: hr=0x%8lx", hr );
           continue;
        }

        hr = filter_graph_handler->AddFilter( *p_p_downstream, l.var_bstr.bstrVal );
        if( FAILED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Cannot add filter, trying another: hr=0x%8lx", hr );
            continue;
        }

        hr = ConnectFilterPins( p_upstream, *p_p_downstream );
        if( SUCCEEDED( hr ) )
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: Connected %s to Downstream", l.psz_upstream);
            return S_OK;
        }

        /* Warning: RemoveFilter does an undocumented Release() on pointer */
        hr = filter_graph_handler->RemoveFilter( *p_p_downstream );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "FindFilter: Failed unloading Filter: hr=0x%8lx", hr );
            continue;
        }
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "FindFilter: trying another (not the filter we want, removed)" );
    }
    while( true );

    mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "FindFilter: No filter connected (found)" );
    hr = E_FAIL;
    return hr;
}

/*****************************************************************************
* Connect is called from Build to enumerate and connect pins
*****************************************************************************/
HRESULT BDAGraph::ConnectFilterPins( IBaseFilter* p_upstream, IBaseFilter* p_downstream )
{
    HRESULT             hr = E_FAIL;
    class localComPtr
    {
        public:
        IPin*      p_pin_upstream;
        IPin*      p_pin_downstream;
        IEnumPins* p_pin_upstream_enum;
        IEnumPins* p_pin_downstream_enum;
        IPin*      p_pin_temp;
        char*      psz_upstream;
        char*      psz_downstream;

        localComPtr():
            p_pin_upstream(NULL), p_pin_downstream(NULL),
            p_pin_upstream_enum(NULL), p_pin_downstream_enum(NULL),
            p_pin_temp(NULL),
            psz_upstream( NULL ),
            psz_downstream( NULL )
            { };
        ~localComPtr()
        {
            if( p_pin_temp )
                p_pin_temp->Release();
            if( p_pin_downstream )
                p_pin_downstream->Release();
            if( p_pin_upstream )
                p_pin_upstream->Release();
            if( p_pin_downstream_enum )
                p_pin_downstream_enum->Release();
            if( p_pin_upstream_enum )
                p_pin_upstream_enum->Release();
            if( psz_upstream )
                delete[] psz_upstream;
            if( psz_downstream )
                delete[] psz_downstream;
        }
    } l;

    PIN_DIRECTION pin_dir;

    //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "ConnectFilterPins: BEGIN" );
    hr = p_upstream->EnumPins( &l.p_pin_upstream_enum );
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot get upstream filter enumerator: hr=0x%8lx", hr );
        return hr;
    }

    do
    {
        if( l.p_pin_upstream )
        {
            l.p_pin_upstream ->Release();
            l.p_pin_upstream = NULL;
        }

        if( !l.p_pin_upstream_enum )
            break;

        hr = l.p_pin_upstream_enum->Next( 1, &l.p_pin_upstream, 0 );
        if( hr != S_OK )
            break;

        hr = GetPinName( l.p_pin_upstream, &l.psz_upstream );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot GetPinName: hr=0x%8lx", hr );
            return hr;
        }

        hr = l.p_pin_upstream->QueryDirection( &pin_dir );
        if( FAILED( hr ) )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot get upstream filter pin direction: hr=0x%8lx", hr );
            return hr;
        }

        hr = l.p_pin_upstream->ConnectedTo( &l.p_pin_downstream );
        if( SUCCEEDED( hr ) )
        {
            l.p_pin_downstream->Release();
            l.p_pin_downstream = NULL;
        }

        if( FAILED( hr ) && hr != VFW_E_NOT_CONNECTED )
        {
            mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot check upstream filter connection: hr=0x%8lx", hr );
            return hr;
        }

        if( ( pin_dir == PINDIR_OUTPUT ) && ( hr == VFW_E_NOT_CONNECTED ) )
        {
            /* The upstream pin is not yet connected so check each pin on the downstream filter */
            hr = p_downstream->EnumPins( &l.p_pin_downstream_enum );
            if( FAILED( hr ) )
            {
                mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot get downstream filter enumerator: hr=0x%8lx", hr );
                return hr;
            }

            do
            {
                if( l.p_pin_downstream )
                {
                    l.p_pin_downstream ->Release();
                    l.p_pin_downstream = NULL;
                }
                if( !l.p_pin_downstream_enum )
                    break;

                hr = l.p_pin_downstream_enum->Next( 1, &l.p_pin_downstream, 0 );
                if( hr != S_OK )
                	break;

                hr = GetPinName( l.p_pin_downstream, &l.psz_downstream );
                if( FAILED( hr ) )
                {
                    mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot GetPinName: hr=0x%8lx", hr );
                    return hr;
                }

                hr = l.p_pin_downstream->QueryDirection( &pin_dir );
                if( FAILED( hr ) )
                {
                    mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot get downstream filter pin direction: hr=0x%8lx", hr );
                    return hr;
                }

                /* Looking for a free Pin to connect to
                 * A connected Pin may have an reference count > 1
                 * so Release and nullify the pointer */
                hr = l.p_pin_downstream->ConnectedTo( &l.p_pin_temp );
                if( SUCCEEDED( hr ) )
                {
                    l.p_pin_temp->Release();
                    l.p_pin_temp = NULL;
                }

                if( hr != VFW_E_NOT_CONNECTED )
                {
                    if( FAILED( hr ) )
                    {
                        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Connect: Cannot check downstream filter connection: hr=0x%8lx", hr );
                        return hr;
                    }
                }

                if( ( pin_dir == PINDIR_INPUT ) && ( hr == VFW_E_NOT_CONNECTED ) )
                {
                    hr = filter_graph_handler->ConnectDirect( l.p_pin_upstream, l.p_pin_downstream, NULL );
                    if( SUCCEEDED( hr ) )
                    {
                        /* If we arrive here then we have a matching pair of pins. */
                        return S_OK;
                    }
                }
                /* If we arrive here it means this downstream pin is not suitable so try the next downstream pin. */
            }
            while( true );
            /* If we arrive here then we ran out of pins before we found a suitable one */
            if( l.p_pin_downstream_enum )
            {
                l.p_pin_downstream_enum->Release();
                l.p_pin_downstream_enum = NULL;
            }
            if( l.p_pin_downstream )
            {
                l.p_pin_downstream->Release();
                l.p_pin_downstream = NULL;
            }
        }
        /* If we arrive here it means this upstream pin is not suitable so try the next upstream pin */
    }
    while( true );
    //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Connect: we did not find any pair of suitable pins (no pins connected)" );
    return E_FAIL;
}

/******************************************************************************
* SampleCB - Callback when the Sample Grabber has a sample
******************************************************************************/
STDMETHODIMP BDAGraph::SampleCB( double /*date*/, IMediaSample *p_sample )
{
    LONG sample_length;
    BYTE *sample_ptr;
    //MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "BDA SampleCB START");

    if( p_sample->IsDiscontinuity() == S_OK )
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_ERROR, "BDA SampleCB: Sample Discontinuity.");
        if(this->is_discontinuity)
        {
            if(this->samplecb_counter < MPXPLAY_DRVDTV_INITIAL_CONTINUITY_LIMIT)
                this->BuffersAllReset();
            return S_OK;
        }
        this->is_discontinuity = TRUE;
    }
    else
    {
    	this->is_discontinuity = FALSE;
    	this->samplecb_counter++;
    	if(this->samplecb_counter == MPXPLAY_DRVDTV_INITIAL_CONTINUITY_LIMIT)
    	{
    		this->BuffersAllReadUnlock();
    		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_ERROR, "BDA SampleCB: counter %d buf:%8.8X next:%8.8X f:%8.8X", this->samplecb_counter, (unsigned int)this->data_buffers_chain,
    		 ((this->data_buffers_chain)? this->data_buffers_chain->next_databuffer : NULL), ((this->data_buffers_chain)? this->data_buffers_chain->buffer_flags : 0));
    	}
    }

    sample_ptr = NULL;
    p_sample->GetPointer( &sample_ptr );
    sample_length = p_sample->GetActualDataLength();

    if((sample_length > 0) && sample_ptr )
    {
    	struct dtvbda_data_buffer_s *dbufchain;
    	PDS_THREADS_MUTEX_LOCK(&this->data_bufferchain_mutex, MPXPLAY_DRVDTV_DEFAULT_TIMEOUTMS_MUTEX);
    	dbufchain = this->data_buffers_chain;
    	while(dbufchain)
    	{
    		// TODO: program and EPG demux
    		//MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_SAMPLCB, "BDA BuffersAllRefill buf:%8.8X sp:%8.8X sl:%d", (unsigned int)dbufchain, (unsigned int)sample_ptr, sample_length);
    		dtvbda_databuffer_putdata(dbufchain, sample_ptr, sample_length);
    		dbufchain = dbufchain->next_databuffer;
    	}
    	PDS_THREADS_MUTEX_UNLOCK(&this->data_bufferchain_mutex);
     }
//     MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_SAMPLCB, "BDA SampleCB: size:%d", (mpxp_int32_t)sample_length);
     return S_OK;
}

STDMETHODIMP BDAGraph::BufferCB( double /*date*/, BYTE* /*buffer*/, long /*buffer_len*/ )
{
    return S_OK;
}

/*****************************************************************************
* Start uses MediaControl to start the graph
*****************************************************************************/
HRESULT BDAGraph::StartFilter()
{
    OAFilterState i_state = State_Stopped;
    HRESULT hr = S_OK;
    int retry = 10;

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Start: BEGIN" );

    this->samplecb_counter = 0;

    if( !media_control_handler )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Start: Media Control has not been created!" );
        return E_FAIL;
    }

    do{
    	media_control_handler->Run();
    	if(((hr = media_control_handler->GetState( 100, &i_state)) == S_OK ) && (i_state == State_Running))
    		break;
    	if(retry == 3)
    		media_control_handler->Stop();
    	if(retry & 1)
    	{
    		MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Start: Trying to start Media Control %d ...", retry);
    	}
    	pds_threads_sleep(100);
	}while(--retry);

    if(i_state == State_Running)
    {
        MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Start: Graph start RUNNING");
        return S_OK;
    }

    mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Start: Graph not started !!! (status: %d)", (int)i_state );
    hr = media_control_handler->StopWhenReady();
    if( FAILED( hr ) )
    {
        mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "Start: Cannot stop Graph after Run failed: hr=0x%8lx", hr );
        return hr;
    }

    return E_FAIL;
}

/******************************************************************************
* removes each filter from the graph
******************************************************************************/
HRESULT BDAGraph::StopFilter()
{
	HRESULT hr = S_OK;

    if( media_control_handler )
    {
    	int retry;

    	// Stop() : The Filter Graph Manager pauses all the filters in the graph, and then calls the IMediaFilter::Stop method
    	//          on all filters, without waiting for the pause operations to complete (!)
    	// https://msdn.microsoft.com/en-us/library/windows/desktop/dd390178(v=vs.85).aspx

    	media_control_handler->Pause();

		retry = 10;
		do{
			OAFilterState i_state = State_Running;
			pds_threads_sleep(50);
			if(((hr = media_control_handler->GetState(100, &i_state)) == S_OK ) && (i_state != State_Running))
				break;
		}while(--retry);

        media_control_handler->Stop();

        retry = 10;
        do{
        	OAFilterState i_state = State_Running;
        	pds_threads_sleep(50);
        	if(((hr = media_control_handler->GetState(100, &i_state)) == S_OK ) && (i_state == State_Stopped))
        		break;
        	if(retry < 5)
        		media_control_handler->Stop();
        }while(--retry);

        if(!retry)
        {
        	MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUGOUT_WARNING, "Destroy: couldn't stop media control !!!" );
        }
    }

	return hr;
}
HRESULT BDAGraph::DestroyFilter()
{
    HRESULT hr = S_OK;
    ULONG mem_ref = 0;

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: BEGIN" );

    this->StopFilter();

    this->DeregisterFilter();

    guid_network_type = GUID_NULL;

    if(media_control_handler)
    {
        mem_ref = media_control_handler->Release();
        if(mem_ref != 0)
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: Media control mem_ref: mem_ref=%ld", mem_ref );
        }
        media_control_handler = NULL;
    }

    if(p_grabber)
    {
        mem_ref = p_grabber->Release();
        if(mem_ref != 0)
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: Sample grabber mem_ref (varies): mem_ref=%ld", mem_ref );
        }
        p_grabber = NULL;
    }

    if(filter_graph_handler)
    {
    	IEnumFilters *pEnum = NULL;
    	HRESULT hr = filter_graph_handler->EnumFilters(&pEnum);
    	if (SUCCEEDED(hr))
    	{
    		IBaseFilter *pFilter = NULL;
    		while (S_OK == pEnum->Next(1, &pFilter, NULL))
    		{
    			filter_graph_handler->RemoveFilter(pFilter);
    			pEnum->Reset();
    			pFilter->Release();
    		}
    		pEnum->Release();
    	}
    	p_transport_info = NULL;
    	p_mpeg_demux = NULL;
    	p_sample_grabber = NULL;
    	p_capture_device = NULL;
    	p_tuner_device = NULL;
    	p_network_provider = NULL;
    	filter_graph_handler = NULL;
    }

    if(dvb_tunerequest_handler)
    {
       	mem_ref = dvb_tunerequest_handler->Release();
        if(mem_ref != 0)
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: DVB Tune request mem_ref=%ld", mem_ref );
        }
        dvb_tunerequest_handler = NULL;
    }

    if(dvbt_locator_handler)
    {
    	mem_ref = dvbt_locator_handler->Release();
    	if(mem_ref != 0)
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: DVB-T locator mem_ref=%ld", mem_ref );
        }
    	dvbt_locator_handler = NULL;
    }

    if(tune_request_handler)
    {
    	mem_ref = tune_request_handler->Release();
        if(mem_ref != 0)
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: Tune request mem_ref=%ld", mem_ref );
        }
        tune_request_handler = NULL;

    }

    if(p_scanning_tuner)
    {
        mem_ref = p_scanning_tuner->Release();
        if(mem_ref != 0)
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: Scanning tuner mem_ref (normally 2 if warm, 3 if active): mem_ref=%ld", mem_ref );
        }
        p_scanning_tuner = NULL;
    }

    if(p_system_dev_enum)
    {
        mem_ref = p_system_dev_enum->Release();
        if(mem_ref != 0)
        {
            MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: system_dev_enum mem_ref: mem_ref=%ld", mem_ref );
        }
        p_system_dev_enum = NULL;
    }

    l_tuner_used = -1;

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "Destroy: FINISHED" );
    return S_OK;
}

/*****************************************************************************
* Add/Remove a DirectShow filter graph to/from the Running Object Table.
* Allows GraphEdit to "spy" on a remote filter graph.
******************************************************************************/
HRESULT BDAGraph::RegisterFilter()
{
    HRESULT   hr = S_OK;
    WCHAR     pwsz_graph_name[128];

    if(!p_ro_table)
    {
    	hr = ::GetRunningObjectTable(0, &p_ro_table);
    	if(FAILED(hr) || !p_ro_table)
    	{
    		mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "RegisterFilter: Cannot get ROT: hr=0x%8lx", hr);
    		goto err_out_register;
    	}
    }

    if(!p_moniker)
    {
    	_snwprintf(pwsz_graph_name, 128, L"MMC_BDA_Graph_%08x_Pid_%08x_Cnt_%08x", (DWORD_PTR) filter_graph_handler, ::GetCurrentProcessId(), pds_gettimeh());
    	hr = CreateItemMoniker(L"!", pwsz_graph_name, &p_moniker);
    	if(FAILED(hr) || !p_moniker)
    	{
    		mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "RegisterFilter: Cannot Create Moniker: hr=0x%8lx", hr);
    		goto err_out_register;
    	}
    }

    if(!d_graph_register)
    {
    	hr = p_ro_table->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, filter_graph_handler, p_moniker, &d_graph_register);
    	if(FAILED(hr) || !d_graph_register)
    	{
    		mpxplay_debugf(MPXPLAY_DEBUGOUT_WARNING, "RegisterFilter: Cannot Register Graph: hr=0x%8lx", hr);
    		goto err_out_register;
    	}
    }

    MPXPLAY_DEBUG_MESSAGE(MPXPLAY_DEBUG_OUTPUT, "RegisterFilter: registered Graph: %S", pwsz_graph_name);
    return hr;

err_out_register:
    DeregisterFilter();
    return hr;
}

void BDAGraph::DeregisterFilter()
{
    if(p_ro_table)
    {
    	if(d_graph_register)
    		p_ro_table->Revoke(d_graph_register);
    	p_ro_table->Release();
    	p_ro_table = NULL;
    }
    if(p_moniker)
    {
    	p_moniker->Release();
    	p_moniker = NULL;
    }
    d_graph_register = 0;
}

HRESULT BDAGraph::GetFilterName(IBaseFilter* p_filter, char** psz_bstr_name)
{
    FILTER_INFO filter_info;
    HRESULT     hr;

    hr = p_filter->QueryFilterInfo(&filter_info);
    if(FAILED(hr))
        return hr;
    int i_name_len = WideCharToMultiByte( CP_ACP, 0, filter_info.achName, -1, *psz_bstr_name, 0, NULL, NULL );
    *psz_bstr_name = new char[ i_name_len ];
    i_name_len = WideCharToMultiByte( CP_ACP, 0, filter_info.achName, -1, *psz_bstr_name, i_name_len, NULL, NULL );

    if( filter_info.pGraph )
        filter_info.pGraph->Release();

    return S_OK;
}

HRESULT BDAGraph::GetPinName(IPin* p_pin, char** psz_bstr_name)
{
    PIN_INFO    pin_info;
    HRESULT     hr;

    hr = p_pin->QueryPinInfo(&pin_info);
    if(FAILED(hr))
        return hr;
    int i_name_len = WideCharToMultiByte( CP_ACP, 0, pin_info.achName, -1, *psz_bstr_name, 0, NULL, NULL );
    *psz_bstr_name = new char[ i_name_len ];
    i_name_len = WideCharToMultiByte( CP_ACP, 0, pin_info.achName, -1, *psz_bstr_name, i_name_len, NULL, NULL );

    if( pin_info.pFilter )
        pin_info.pFilter->Release();

    return S_OK;
}

#endif // MPXPLAY_GUI_QT
