/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: hxaudstr_new.cpp,v 1.21.2.1 2004/07/09 02:08:08 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hlxclib/stdio.h" 
#include "hlxclib/string.h"



#include "hxresult.h"
#include "hxtypes.h"

#include "hxcom.h"
#include "hxengin.h"
#include "ihxpckts.h"
#include "hxbuffer.h"
#include "hxausvc.h"
#include "hxrasyn.h"
#include "hxprefs.h"
#include "hxerror.h"

#include "errdbg.h"
#include "chxpckts.h"
#include "hxaudply.h"
#include "hxaudstr.h"
#include "hxaudses.h"
#include "hxaudvol.h"	

#include "mixengine.h"

#include "hxslist.h"
#include "hxmap.h"
#include "auderrs.h"

#include "hxtick.h"

#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static const char HX_THIS_FILE[] = __FILE__;
#endif

#define CACHE_INCREMENT_SIZE	2

//#define _TESTING    1
#ifdef _TESTING
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#if defined (_WINDOWS) || defined (_WIN32)

#include <io.h>

#endif

int g_log = -1;
#endif

/************************************************************************
 *  Method:
 *		IHXAudioStream::CHXAudioStream()
 *	Purpose:
 *		Constructor. 
 */
CHXAudioStream::CHXAudioStream(CHXAudioPlayer* owner, IUnknown* pContext)
:       m_lRefCount(0)
,       m_wLastError(HXR_OK)
,       m_pResampler(NULL)
,       m_pValues(0)
,       m_bDisableWrite(FALSE)
,       m_ulGranularity(0)
,	m_ulInputBytesPerGran(0)
,	m_ulOutputBytesPerGran(0)
,       m_pDataList(0)
,	m_pInstantaneousList(0)
,	m_pRAByToTsInList(0)
,	m_pRAByToTsAdjustedList(0)
,       m_bFirstWrite(TRUE)
,	m_bHooksInitialized(FALSE)
,       m_bInited(FALSE)
,	m_bSetupDone(FALSE)
,	m_bAudioFormatKnown(FALSE)
,       m_ulMaxBlockSize(0)
,       m_uVolume(HX_MAX_VOLUME)
,	m_bMute(FALSE)
,       m_bGotHooks(FALSE)
,	m_llLastWriteTime(0)
,	m_ulFudge(5)
,	m_pInDataPtr(0)
,       m_pOutDataPtr(0)
,	m_bTobeTimed(TRUE)
,	m_bIsFirstPacket(TRUE)
,	m_bIsLive(FALSE)
,	m_ulBaseTime(0)
,	m_ulLiveDelay(0)
,	m_bSetupToBeDone(FALSE)
,       m_bCrossFadingToBeDone(FALSE)
,       m_pCrossFadeStream(NULL)
,       m_llCrossFadeStartTime(0)
,       m_ulCrossFadeDuration(0)
,       m_bFadeToThisStream(FALSE)
,	m_bFadeAlreadyDone(FALSE)
,	m_bRealAudioStream(FALSE)
,	m_ulLastInputStartTime(0)
,	m_ulLastInputEndTime(0)
,	m_llLastStartTimePlayed(0)
,	m_ulTSRollOver(0)
,	m_bLastWriteTimeUpdated(FALSE)
,	m_pCommonClassFactory(NULL)
,	m_pAvailableBuffers(NULL)
,	m_uCacheSize(CACHE_INCREMENT_SIZE)
,	m_bCacheMayBeGrown(FALSE)
,	m_bDeterminedInitialCacheSize(FALSE)
,	m_bLastNMilliSecsToBeSaved(FALSE)
,	m_ulLastNMilliSeconds(MINIMUM_INITIAL_PUSHDOWN)
,	m_pLastNMilliSecsList(NULL)
,	m_ulLastNHeadTime(0)
,	m_ulLastNTailTime(0)
, 	m_eState(E_STOPPED)
,	m_bCanBeRewound(FALSE)
,	m_bAudioDeviceReflushHint(FALSE)
,	m_bIsResumed(FALSE)
,	m_bPlayerPause(FALSE)
,       m_pPreferences(NULL)
,	m_bMayNeedToRollbackTimestamp(FALSE)
,	m_piPendingAudioData(NULL)
{
    m_Owner	    = owner;
    if (m_Owner)
    {
	m_Owner->AddRef();
    }

    if (pContext)
    {
	HX_VERIFY(HXR_OK == pContext->QueryInterface(IID_IHXCommonClassFactory, 
					    (void**) &m_pCommonClassFactory));
    }

#ifdef HELIX_FEATURE_VOLUME
    m_pStreamVolume = NULL;
#endif
    
#if defined(HELIX_FEATURE_PREFERENCES)
    if (pContext)
    {
	HX_VERIFY(HXR_OK == pContext->QueryInterface(IID_IHXPreferences, (void**) &m_pPreferences));
    }
#endif /* HELIX_FEATURE_PREFERENCES */

    m_DryNotificationMap = new CHXMapPtrToPtr;
    m_pInDataPtr    = new HXAudioData;
    m_pOutDataPtr   = new HXAudioData;
    m_pMixEngine    = new HXAudioSvcMixEngine() ;
};

/************************************************************************
 *  Method:
 *		IHXAudioStream::~CHXAudioStream()
 *	Purpose:
 *		Destructor. Clean up and set free.
 */
CHXAudioStream::~CHXAudioStream()
{
    HX_DELETE(m_DryNotificationMap);
    ResetStream();
    HX_RELEASE(m_piPendingAudioData);
}
 
/////////////////////////////////////////////////////////////////////////
//  Method:
//      IUnknown::QueryInterface
//  Purpose:
//      Implement this to export the interfaces supported by your
//      object.
//
STDMETHODIMP CHXAudioStream::QueryInterface(REFIID riid, void** ppvObj)
{
    QInterfaceList qiList[] =
        {
            { GET_IIDHANDLE(IID_IHXAudioStream), (IHXAudioStream*)this },
            { GET_IIDHANDLE(IID_IHXRealAudioSync), (IHXRealAudioSync*)this },
            { GET_IIDHANDLE(IID_IHXAudioStream2), (IHXAudioStream2*)this },
            { GET_IIDHANDLE(IID_IHXCommonClassFactory), (IHXCommonClassFactory*)this },
            { GET_IIDHANDLE(IID_IHXUpdateProperties), (IHXUpdateProperties*)this },
            { GET_IIDHANDLE(IID_IUnknown), (IUnknown*)(IHXAudioStream*)this },
#ifdef HELIX_FEATURE_VOLUME            
            { GET_IIDHANDLE(IID_IHXVolumeAdviseSink), (IHXVolumeAdviseSink*)this },
#endif            
        };
    
    return ::QIFind(qiList, QILISTSIZE(qiList), riid, ppvObj);
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//      IUnknown::AddRef
//  Purpose:
//      Everyone usually implements this the same... feel free to use
//      this implementation.
//
STDMETHODIMP_(ULONG32) CHXAudioStream::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//      IUnknown::Release
//  Purpose:
//      Everyone usually implements this the same... feel free to use
//      this implementation.
//
STDMETHODIMP_(ULONG32) CHXAudioStream::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;
    return 0;
}

/*
 *  IHXAudioStream methods
 */

/************************************************************************
 *  Method:
 *		IHXAudioStream::Init
 *	Purpose:
 *		Init the audio stream.
 */
STDMETHODIMP CHXAudioStream::Init
(
    const HXAudioFormat*	pAudioFormat,
          IHXValues*		pValues
)
{
    if (m_bAudioFormatKnown)
    {
	return HXR_OK;
    }

    HX_RESULT theErr = HXR_OK;
    m_pValues = pValues;
    if (m_pValues)
    {
	m_pValues->AddRef();
	UINT32 ulVal = 0;
	m_pValues->GetPropertyULONG32("audioDeviceReflushHint", ulVal);
	if (ulVal == 1)
	{
	    SetAudioDeviceReflushHint(TRUE);
	    m_Owner->m_Owner->CheckIfLastNMilliSecsToBeStored();
	}
    }

    memcpy( &m_AudioFmt, pAudioFormat, sizeof(HXAudioFormat) );

    // Create the audio data list 
    m_pDataList = new CHXSimpleList;
    if ( !m_pDataList )
        theErr = HXR_OUTOFMEMORY;
    if(!theErr) // check if list constructor really succeeded
    {
        if(!m_pDataList->IsPtrListValid())
            theErr = HXR_OUTOFMEMORY;
    }

    m_pInstantaneousList = new CHXSimpleList;
    if ( !m_pInstantaneousList || !m_pInstantaneousList->IsPtrListValid())
        theErr = HXR_OUTOFMEMORY;

    // Reset this so that we init the hooks
    m_bFirstWrite	= TRUE;
    m_bHooksInitialized	= FALSE;

#ifdef HELIX_FEATURE_VOLUME    
    if( !theErr )
    {
        m_pStreamVolume = (IHXVolume*)new CHXVolume;
        if( m_pStreamVolume )
        {
            m_pStreamVolume->AddRef();
            m_pStreamVolume->AddAdviseSink(this);
        }
        else
            theErr = HXR_OUTOFMEMORY;
    }
#endif    

    m_bAudioFormatKnown = TRUE;

    if (m_bSetupToBeDone)
    {
	m_bSetupToBeDone    = FALSE;
	m_Owner->AudioFormatNowKnown();
    }

    if (!theErr && m_bSetupDone && !m_bInited)
    {
	theErr = ProcessInfo();
    }

    return theErr;
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::Write
 *  Purpose:
 *      Write audio data to Audio Services. 
 *
 *      NOTE: If the renderer loses packets and there is no loss
 *      correction, then the renderer should write the next packet 
 *      using a meaningful start time.  Audio Services will play 
 *      silence where packets are missing.
 */
STDMETHODIMP CHXAudioStream::Write
( 
    HXAudioData*	pInData
)
{
    HX_RESULT theErr = HXR_OK;

    if (!pInData)
    {
	return HXR_INVALID_PARAMETER;
    }

    if (!m_bInited)
    {
	return HXR_NOT_INITIALIZED;
    }

    // Init pre-mix hooks. Call this once to set up hook info. 
    if ( !m_bHooksInitialized )
    {
	InitHooks();
    }

#if defined(HELIX_FEATURE_AUDIO_INCOMPLETESAMPLE)
    /* make sure that we are always handing complete sample frames
     * down the chain by buffering up samples if we don't get complete frames.
     *
     * This is done by using a slush IHXBuffer that is just large enough
     * to hold one sample frame, and a second IHXBuffer to hold sample fragments.
     * This one does not own a buffer, but uses the incoming buffers.
     *
     * This way, we don't need any large memcpy()s. Unfortunately, we have to
     * create a new slush buffer every time because we don't know how long
     * this buffer will be stuck in the queue until it gets rendered.
     *
     * I guess we could try to be smart and only create a new one if the old one
     * is still AddRef()ed but I figure it's not worth it.
     */

    /* if there was a discontinuity in the audio stream, throw away pending bytes.
     * This also assumes that we're starting at a sample frame boundary, which might
     * be wrong -- but we really have no way to tell.
     */

    if (pInData->uAudioStreamType != STREAMING_AUDIO)
    {
	m_ulPendingAudioBytes = 0 ;
	HX_RELEASE(m_piPendingAudioData) ;
    }

    /* if pData is NULL, hand it through unchanged. See comment in Write2() */
    if (!pInData->pData)
	return Write2(pInData) ;

    UINT32 ulInBytes = pInData->pData->GetSize() ; // number of bytes in incoming sample
    UINT32 ulCutoffBytes = 0 ; // number of bytes that will be cut off incoming sample

    /* first check if we have pending samples. */
    if (m_ulPendingAudioBytes)
    {
	// complete sample frames would have been sent the last time around
	HX_ASSERT(m_ulPendingAudioBytes < m_ulSampleFrameSize) ;
	HX_ASSERT(m_piPendingAudioData) ;

	/* append more bytes from the start of the incoming packet. */
	ulCutoffBytes = m_ulSampleFrameSize - m_ulPendingAudioBytes ;
	if (ulCutoffBytes > ulInBytes)
	    ulCutoffBytes = ulInBytes ;

	memcpy(m_piPendingAudioData->GetBuffer() + m_ulPendingAudioBytes,
	       pInData->pData->GetBuffer(),
	       ulCutoffBytes ) ;

	m_ulPendingAudioBytes += ulCutoffBytes ;
	ulInBytes             -= ulCutoffBytes ;
    }

    // if we have a complete sample frame in the slush buffer, send it.
    if (m_ulPendingAudioBytes == m_ulSampleFrameSize)
    {
	HX_ASSERT(m_piPendingAudioData) ;

	HXAudioData audioData ;

	audioData.pData = m_piPendingAudioData ;
	audioData.pData->AddRef() ;

	// use time of incoming packet -- one sample frame is below the
	// ms resolution of time stamps.
	audioData.ulAudioTime = pInData->ulAudioTime;
	// stream type is incoming stream type
	audioData.uAudioStreamType = pInData->uAudioStreamType;
	theErr = Write2(&audioData) ;
	audioData.pData->Release() ;

	m_ulPendingAudioBytes = 0 ;

	// release the slush buffer
	HX_RELEASE(m_piPendingAudioData) ;

	if (FAILED(theErr))
	    return theErr ;
    }

    // put partial sample frames from the end of the incoming buffer
    // into the slush buffer.

    if (ulInBytes % m_ulSampleFrameSize)
    {
	// the slush buffer should be empty here.
	HX_ASSERT(m_ulPendingAudioBytes == 0);
	HX_ASSERT(m_piPendingAudioData == 0) ;

	// reserve a new slush buffer
	theErr = CreateInstance(IID_IHXBuffer, (void**)&m_piPendingAudioData);

	if (SUCCEEDED(theErr))
	    theErr = m_piPendingAudioData->SetSize(m_ulSampleFrameSize) ;

	if (SUCCEEDED(theErr))
	{
	    m_ulPendingAudioBytes = ulInBytes % m_ulSampleFrameSize ;
	    ulInBytes -= m_ulPendingAudioBytes ;

	    memcpy(m_piPendingAudioData->GetBuffer(),
		   pInData->pData->GetBuffer() + ulCutoffBytes + ulInBytes,
		   m_ulPendingAudioBytes) ;
	}

	if (FAILED(theErr))
	    return theErr ;
    }

    // send any leftover fragment of the incoming buffer.

    if (ulInBytes == pInData->pData->GetSize() && !ulCutoffBytes)
    /* this is the entire buffer, not a fragment. 
     * This is the normal case -- let's handle it efficiently. */
    {
	theErr = Write2(pInData) ;
    }
    else if (ulInBytes)
    /* if anything left in buffer, send it in a fragment */
    {
	HXAudioData audioData ;
	CHXBufferFragment* pFragment = new CHXBufferFragment(
	    pInData->pData,
	    pInData->pData->GetBuffer() + ulCutoffBytes,
	    ulInBytes);

	theErr = pFragment->QueryInterface(IID_IUnknown, (void**)&audioData.pData) ;

	// this must always succeed, since we know it exports a IHXBuffer
	HX_ASSERT(SUCCEEDED(theErr)) ;

	// use time of incoming packet -- one sample frame is below the
	// ms resolution of time stamps.
	audioData.ulAudioTime = pInData->ulAudioTime;
	// stream type is incoming stream type if we did not cut anything away,
	// and STREAMED_AUDIO if we did (because in that case this is a continuation)
	audioData.uAudioStreamType = ulCutoffBytes ? STREAMING_AUDIO : pInData->uAudioStreamType ;
	theErr = Write2(&audioData) ;
	// we release our hold on pFragment here. When MixIntoBuffer() is done with
	// this fragment, it will also release its hold, and the fragment gets
	// deleted.
	audioData.pData->Release() ;
    }
#else
    theErr = Write2(pInData) ;
#endif /* HELIX_FEATURE_AUDIO_INCOMPLETESAMPLE */

    return theErr;
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::Write2
 *  Purpose:
 *      Write audio data to Audio Services. This is a companion/backend
 *      function to IHXAudioStream::Write
 *
 */
HX_RESULT CHXAudioStream::Write2(HXAudioData* pInData)
{
    HX_RESULT theErr = HXR_OK;

    // Process any "hooks"; Add the data to the data list.
    /* If buffer is NULL, it means that the user just 
     * wants to know what timestamp should be placed in the next 
     * STREAMED/TIMED audio data
     */
    if ( !m_bGotHooks || !pInData->pData)
    {
	theErr = AddData( pInData );
    }
    else
    {
 	HXAudioData outData;
	
	outData.pData	    = 0;
	outData.ulAudioTime = 0;

	theErr = ProcessHooks( pInData, &outData );
	if (!theErr && !m_bDisableWrite )
	{
	    theErr = AddData( &outData );
	}

	if (outData.pData)
	{
	    outData.pData->Release();
	}
    }
    
    return theErr;
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::AddPreMixHook
 *	Purpose:
 *      Use this method to add a pre-mix audio data hook.
 */
STDMETHODIMP CHXAudioStream::AddPreMixHook
( 
	  IHXAudioHook*    pHook,
    const BOOL              bDisableWrite
)
{
#if defined(HELIX_FEATURE_AUDIO_PREMIXHOOK)
    void* pTmp = 0;
    
    /* Does one already exists */
    if (m_PreMixHookMap.Lookup((void*)pHook, pTmp))
    {
	return HXR_INVALID_PARAMETER;
    }

    HXAudioHookInfo* pPreMixHookInfo = (HXAudioHookInfo*) new HXAudioHookInfo;
    pPreMixHookInfo->pHook   	    = pHook;
    pPreMixHookInfo->bDisableWrite  = bDisableWrite;
    pPreMixHookInfo->bFinal = FALSE;
    pPreMixHookInfo->bIgnoreAudioData = FALSE;
    pPreMixHookInfo->bMultiChannelSupport = FALSE;

    IHXValues* pValues = NULL;
    if (pHook && pHook->QueryInterface(IID_IHXValues, (void**) &pValues) == HXR_OK)
    {
	UINT32 ulValue = 0;
	pValues->GetPropertyULONG32("IgnoreAudioData", ulValue);
	pPreMixHookInfo->bIgnoreAudioData = (ulValue == 1);
	HX_RELEASE(pValues);
    }

    pHook->AddRef();		// Released in destructor

    IHXAudioMultiChannel* pMultiChannel = NULL;
    if (pHook && HXR_OK == pHook->QueryInterface(IID_IHXAudioMultiChannel, (void**) &pMultiChannel))
    {
        pPreMixHookInfo->bMultiChannelSupport = pMultiChannel->GetMultiChannelSupport();
    }
    HX_RELEASE(pMultiChannel);

    m_PreMixHookMap.SetAt(pHook, pPreMixHookInfo);

    m_bGotHooks	= TRUE;

    /* If any one of them is Disabled, we do not write */
    if (bDisableWrite)
    {
	m_bDisableWrite = TRUE;
    }

    ProcessAudioHook(ACTION_ADD, pHook);

    /* If we are already initialized, send the audio format */
    if (m_bHooksInitialized)
    {
	if (pPreMixHookInfo->bIgnoreAudioData ||
	    HXR_OK == ProcessAudioHook(ACTION_CHECK, pHook))
	{
	    pHook->OnInit( &m_AudioFmt );
	}
    }

    return HXR_OK;
#else
    return HXR_NOTIMPL;
#endif /* HELIX_FEATURE_AUDIO_PREMIXHOOK */
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::RemovePreMixHook
 *	Purpose:
 *      Use this method to remove a pre-mix audio data hook.
 */
STDMETHODIMP CHXAudioStream::RemovePreMixHook
( 
      IHXAudioHook*    pHook
)
{
#if defined(HELIX_FEATURE_AUDIO_PREMIXHOOK)
    HXAudioHookInfo* pPreMixHookInfo = 0;
    BOOL bCheckForDisableWrite	      = FALSE;

    if (!m_PreMixHookMap.Lookup((void*)pHook, (void*&) pPreMixHookInfo))
    {
	return HXR_INVALID_PARAMETER;
    }

    m_PreMixHookMap.RemoveKey(pHook);

    /* If we are removing a hook which had disable write, 
     * we need to re-determine if any of the remaining hooks
     * has DisableWrite set to TRUE
     */
    if (pPreMixHookInfo->bDisableWrite)
    {
	bCheckForDisableWrite = TRUE;
	m_bDisableWrite	      = FALSE;
    }

    ProcessAudioHook(ACTION_REMOVE, pHook);

    pPreMixHookInfo->pHook->Release();
    delete pPreMixHookInfo;

    if (m_PreMixHookMap.GetCount() == 0)
    {
	m_bGotHooks	= FALSE;
	m_bDisableWrite = FALSE;
    }
    else if (bCheckForDisableWrite)
    {
	CHXMapPtrToPtr::Iterator lIter = m_PreMixHookMap.Begin();
	for (; lIter != m_PreMixHookMap.End(); ++lIter)
	{
	    HXAudioHookInfo* pPreMixHook = (HXAudioHookInfo*) (*lIter);
	    
	    /* atleast one has Disable Write ON */
	    if (pPreMixHook->bDisableWrite)
	    {
		m_bDisableWrite = TRUE;
		break;
	    }
	}
    }
#endif /* HELIX_FEATURE_AUDIO_PREMIXHOOK */

    return HXR_OK;
}

/************************************************************************
*  Method:
*	IHXAudioStream::AddDryNotification
*  Purpose:
*	Use this to add a notification response object to get notifications
*	when audio stream is running dry.
*/
STDMETHODIMP CHXAudioStream::AddDryNotification
			(
			    IHXDryNotification* /*IN*/ pNotification
			)
{
    if (!pNotification)
    {
	return HXR_INVALID_PARAMETER;
    }

    void* pTmp = 0;
    
    /* Does one already exists */
    if (m_DryNotificationMap->Lookup((void*)pNotification, pTmp))
    {
	return HXR_INVALID_PARAMETER;
    }

    pNotification->AddRef();

    m_DryNotificationMap->SetAt((void*)pNotification, (void*)pNotification);

    return HXR_OK;
}


/************************************************************************
*  Method:
*      IHXAudioStream2::RemoveDryNotification
*  Purpose:
*	    Use this to remove itself from the notification response object
*	    during the stream switching.
*/
STDMETHODIMP CHXAudioStream::RemoveDryNotification   
			    (
				IHXDryNotification* /*IN*/ pNotification
			    )
{
    HX_RESULT	hr = HXR_OK;

    void* pTmp = 0;

    if (!pNotification)
    {
	hr = HXR_INVALID_PARAMETER;
	goto cleanup;
    }

    // remove only if it is exists
    if (m_DryNotificationMap->Lookup((void*)pNotification, pTmp))
    {
	m_DryNotificationMap->RemoveKey((void*)pNotification);
	HX_RELEASE(pNotification);
    }
    else
    {
	hr = HXR_INVALID_PARAMETER;
	goto cleanup;
    }

cleanup:

    return hr;
}

/************************************************************************
*  Method:
*      IHXAudioStream2::GetAudioFormat
*  Purpose:
*	    Returns the input audio format of the data written by the 
*	    renderer. This function will fill in the pre-allocated 
*	    HXAudioFormat structure passed in.
*/
STDMETHODIMP
CHXAudioStream::GetAudioFormat(HXAudioFormat*	/*IN/OUT*/pAudioFormat)
{
    HX_ASSERT(pAudioFormat);
    if (!pAudioFormat)
    {
	return HXR_INVALID_PARAMETER;
    }

    if (!m_bInited)
    {
	return HXR_UNEXPECTED;
    }

    pAudioFormat->uChannels	    = m_AudioFmt.uChannels;
    pAudioFormat->uBitsPerSample    = m_AudioFmt.uBitsPerSample;
    pAudioFormat->ulSamplesPerSec   = m_AudioFmt.ulSamplesPerSec;
    pAudioFormat->uMaxBlockSize	    = m_AudioFmt.uMaxBlockSize;

    return HXR_OK;
}


 
/************************************************************************
 *  Method:
 *              IHXAudioStream::GetAudioVolume
 *      Purpose:
 *              Return this stream's IRMA volume interface.
 */
STDMETHODIMP_(IHXVolume*) CHXAudioStream::GetAudioVolume()
{
    IHXVolume* pRet = NULL;
    
#ifdef HELIX_FEATURE_VOLUME    
    if( m_pStreamVolume )
    {
        m_pStreamVolume->AddRef();
        pRet = m_pStreamVolume;
    }
#endif 
   
    return pRet;
}

#if defined(HELIX_FEATURE_VOLUME)
//
// IHXVolume methods
//
STDMETHODIMP CHXAudioStream::OnVolumeChange(const UINT16 uVolume)
{
    m_uVolume = uVolume;
#ifdef HELIX_FEATURE_GAINTOOL
    if (m_pMixEngine)
        m_pMixEngine->SetVolume(m_pMixEngine->HXVolume2TenthOfDB(m_uVolume)) ; 
#endif
    return HXR_OK;
}

STDMETHODIMP CHXAudioStream::OnMuteChange(const BOOL bMute)
{
    m_bMute = bMute;
#ifdef HELIX_FEATURE_GAINTOOL
    if (m_pMixEngine)
        m_pMixEngine->SetVolume(m_pMixEngine->HXVolume2TenthOfDB(bMute ? HX_MIN_VOLUME : m_uVolume)) ;
#endif
    return HXR_OK;
}

#endif /* HELIX_FEATURE_VOLUME */





/************************************************************************
 *  Method:
 *		IHXAudioStream::AddData
 *	Purpose:
 *		Add audio data to list.
 *	NOTE: Mark Streamed data also as Timed data IF we don't write a streamed packet
 *	 since it was LATE!!!
 */
HX_RESULT CHXAudioStream::AddData
(
    HXAudioData* pAudioData
)
{
    HX_RESULT	    theErr = HXR_OK;
    BOOL	    bInTSRollOver = FALSE;
    HXAudioInfo*    pAinfo = 0;

    /* If buffer is NULL, it means that the user just 
     * wants to know what timestamp should be placed in the next 
     * STREAMED/TIMED audio data
     */
    if (!pAudioData->pData)
    {
	HXAudioInfo* pInfo = NULL;

	if (!m_pDataList->IsEmpty() && 
	    NULL != (pInfo = (HXAudioInfo*) m_pDataList->GetTail()))
	{
	    pAudioData->ulAudioTime = pInfo->ulStartTime + 
				      CalcMs(pInfo->pBuffer->GetSize());
	}
	else
	{
	    pAudioData->ulAudioTime = INT64_TO_UINT32(m_llLastWriteTime - CAST_TO_INT64 (m_ulTSRollOver) * CAST_TO_INT64 (MAX_UINT32));
	}

	return HXR_OK;
    }

    // make sure the renderer does not pass NULL data!!
    HX_ASSERT(pAudioData->pData->GetBuffer() != NULL &&
	      pAudioData->pData->GetSize() != 0);
    if (pAudioData->pData->GetBuffer()	== NULL ||
	pAudioData->pData->GetSize()	== 0)
    {
	return HXR_INVALID_PARAMETER;
    }

    if (m_bIsFirstPacket)
    {
	m_bIsFirstPacket = FALSE;
	
	IHXErrorMessages* pErrMsg = NULL;
	if (HXR_OK == m_Owner->m_pContext->QueryInterface(IID_IHXErrorMessages, (void**)&pErrMsg))
	{
	    DEBUG_OUT(pErrMsg, DOL_GENERIC, (s,"AudioFormatIn: %lu channels %lu SamplesPerSec", m_AudioFmt.uChannels, m_AudioFmt.ulSamplesPerSec));
	    DEBUG_OUT(pErrMsg, DOL_GENERIC, (s,"AudioFormatOut: %lu channels %lu SamplesPerSec", m_DeviceFmt.uChannels, m_DeviceFmt.ulSamplesPerSec));
	}
	HX_RELEASE(pErrMsg);

	if (m_bIsLive)
	{
            // XXX wschildbach why do we update a last write time *here*?
	    m_Owner->UpdateStreamLastWriteTime();
	    UpdateStreamLastWriteTime(TRUE);
	}
    }
//{FILE* f1 = ::fopen("c:\\temp\\audio.txt", "a+"); ::fprintf(f1, "%lu\tAddData\t%p\t%lu\n", HX_GET_BETTERTICKCOUNT(), this, pAudioData->ulAudioTime);::fclose(f1);}
//    ::fwrite(pAudioData->pData->GetBuffer(), pAudioData->pData->GetSize(), 1, fdin);

    UINT32 ulDataTime = CalcMs(pAudioData->pData->GetSize());
    UINT32 ulEndTime = pAudioData->ulAudioTime + ulDataTime;

    if (m_pAvailableBuffers && !m_bDeterminedInitialCacheSize && ulDataTime > 0)
    {
	m_bDeterminedInitialCacheSize = TRUE;
	m_uCacheSize = (UINT16) (m_ulGranularity*2/ulDataTime) + 1;
	/* make sure it is atleast CACHE_INCREMENT_SIZE to begin with */
	m_uCacheSize = m_uCacheSize < CACHE_INCREMENT_SIZE ? 
			    CACHE_INCREMENT_SIZE : m_uCacheSize;
    }

    if (m_ulLastInputStartTime > pAudioData->ulAudioTime &&
	((m_ulLastInputStartTime - pAudioData->ulAudioTime) > MAX_TIMESTAMP_GAP))
    {
	bInTSRollOver = TRUE;
	m_ulTSRollOver++;
    }

    m_ulLastInputStartTime  = pAudioData->ulAudioTime; 
    m_ulLastInputEndTime    = ulEndTime;

    /* even in STREAMING_AUDIO case, it might happen, that the packets
     * written are late. e.g. packets received late on the network 
     */

    // XXX wschildbach : Why do we even care? Just insert the packet into the queue
    // and let the reaper take care of it!

    INT64 llActualTimestamp = CAST_TO_INT64 (pAudioData->ulAudioTime) + CAST_TO_INT64 m_ulTSRollOver * CAST_TO_INT64 MAX_UINT32;
    INT64 llActualEndTime = CAST_TO_INT64 (pAudioData->ulAudioTime) + CAST_TO_INT64 (ulDataTime) +
			    CAST_TO_INT64 (m_ulTSRollOver) * CAST_TO_INT64 (MAX_UINT32);

#if 0 // testing sampling frequency estimation for "inaccurate resampling"
    if (pAudioData->uAudioStreamType == TIMED_AUDIO)
    {
        m_startMeasureTime = llActualTimestamp ;
        m_totSamples = 0 ;
    }
    else if (pAudioData->uAudioStreamType == STREAMING_AUDIO)
    {
        float diffTime = (llActualTimestamp - m_startMeasureTime) / 1000.0f ;
        if (diffTime)
        {
            float frEstimate = m_totSamples / m_AudioFmt.uChannels / diffTime ;
            float frEstErr = frEstimate * 0.001f / diffTime ; // 1 ms inaccuracy
            {FILE *f1 = fopen("c:\\temp\\estimatesr.txt","a+");
             fprintf(f1,"%I64d\t%I64d\t%f\t%f\n",llActualTimestamp,llActualEndTime,frEstimate,frEstErr);
             fclose(f1);
            }
        }
    }
    m_totSamples += CalcSamples(pAudioData->pData->GetSize()) ;
#endif

    if ((pAudioData->uAudioStreamType == STREAMING_AUDIO ||
	 pAudioData->uAudioStreamType == TIMED_AUDIO) &&
       !(llActualTimestamp >= m_llLastWriteTime ||
	 llActualEndTime > m_llLastWriteTime)) 
    {
	/* Too late*/
	m_bTobeTimed	= TRUE;
//{FILE* f1 = ::fopen("e:\\audio.txt", "a+"); ::fprintf(f1, "%lu\t%p\t%d\t%lu\t%lu\tLATE\n", HX_GET_BETTERTICKCOUNT(), this, m_pDataList->GetCount(), pAudioData->ulAudioTime, (INT32)m_llLastWriteTime);::fclose(f1);}

	/*
    {FILE *f1 = fopen("c:\\temp\\mix.txt","a+"); fprintf(f1,"LATE packet\n");
     fclose(f1);
    }
    */  
      
        return HXR_LATE_PACKET;	
    }

    pAinfo = new HXAudioInfo;
    if( !pAinfo )
    {
        return HXR_OUTOFMEMORY;
    }

    pAudioData->pData->AddRef();
    pAinfo->pBuffer 		= pAudioData->pData;
    pAinfo->ulStartTime  	= pAudioData->ulAudioTime;
    pAinfo->pOffset     	= pAudioData->pData->GetBuffer();  
    pAinfo->ulBytesLeft		= pAudioData->pData->GetSize();  
    pAinfo->uAudioStreamType    = pAudioData->uAudioStreamType;

    if (m_bTobeTimed && pAudioData->uAudioStreamType == STREAMING_AUDIO)
    {
	pAinfo->uAudioStreamType = TIMED_AUDIO;
	m_bTobeTimed		 = FALSE;
    }
    else if (m_bTobeTimed && pAudioData->uAudioStreamType == TIMED_AUDIO)
    {
	m_bTobeTimed	= FALSE;
    }

//{FILE* f1 = ::fopen("c:\\temp\\audio.txt", "a+"); ::fprintf(f1, "AddData ulAudioTime: %lu\n", pAudioData->ulAudioTime);::fclose(f1);}

//////////////////////////////////////////////////////////////////////
                // XXX wschildbach
    // the start time of this packet in samples. This may be corrected later on.
    pAinfo->llStartTimeInSamples = CAST_TO_INT64(llActualTimestamp) * m_AudioFmt.ulSamplesPerSec / 1000 * m_AudioFmt.uChannels ;
    pAinfo->llEndTimeInSamples   = pAinfo->llStartTimeInSamples + Bytes2Samples(pAinfo->pBuffer->GetSize(), &m_AudioFmt) ;

//////////////////////////////////////////////////////////////////////

    if (pAinfo->uAudioStreamType == INSTANTANEOUS_AUDIO)
    {
	CHXSimpleList* pList = new CHXSimpleList;
        if( !pList )
        {
            HX_RELEASE(pAudioData->pData);
            HX_DELETE(pAinfo);
            return HXR_OUTOFMEMORY;
        }
	pList->AddHead((void*) pAinfo);
	m_pInstantaneousList->AddTail((void*) pList);
	m_Owner->m_Owner->ToBeRewound();
    }
    else if (pAinfo->uAudioStreamType == STREAMING_INSTANTANEOUS_AUDIO)
    {
	HX_ASSERT(m_pInstantaneousList && m_pInstantaneousList->GetCount() > 0);
	CHXSimpleList* pList = NULL;
	if (m_pInstantaneousList->GetCount() == 0)
	{
	    pList = new CHXSimpleList;
            if( !pList )
            {
                HX_RELEASE(pAudioData->pData);
                HX_DELETE(pAinfo);
                return HXR_OUTOFMEMORY;
            }
	    m_pInstantaneousList->AddTail((void*) pList);

	    // fix for naive users!
	    pAinfo->uAudioStreamType = INSTANTANEOUS_AUDIO;
	    m_Owner->m_Owner->ToBeRewound();
	}

	pList = (CHXSimpleList*) m_pInstantaneousList->GetTail();
	pList->AddTail(pAinfo);
    }
    else if (m_pDataList->IsEmpty())
    {
	/*
    {FILE *f1 = fopen("c:\\temp\\mix.txt","a+"); fprintf(f1,"adding to empty list...\n");
     fclose(f1);
    }
	*/
        m_pDataList->AddTail((void*) pAinfo);
    }
    else
    {
/*
    {FILE *f1 = fopen("c:\\temp\\mix.txt","a+"); fprintf(f1,"adding to non-empty list...\n");
     fclose(f1);
    }
*/
	HXAudioInfo* pInfo = (HXAudioInfo*) m_pDataList->GetTail();

	UINT32	ulActualTSRollOver = m_ulTSRollOver;

	if (bInTSRollOver && ulActualTSRollOver)
	{
	    ulActualTSRollOver--;
	}

	INT64 llActualLastEndTime = CAST_TO_INT64 (pInfo->ulStartTime) + CAST_TO_INT64 (CalcMs(pInfo->pBuffer->GetSize())) +
				    CAST_TO_INT64 ulActualTSRollOver * CAST_TO_INT64 MAX_UINT32;
	INT64 llActualLastStartTime = CAST_TO_INT64 (pInfo->ulStartTime) + CAST_TO_INT64 ulActualTSRollOver * CAST_TO_INT64 MAX_UINT32;
	
	if (llActualTimestamp < llActualLastStartTime)
	{
	    /* Not allowed */
	    theErr = HXR_OUTOFORDER_PACKET; 
	    /* something is fu*#$#up... figure out what?*/

	    HX_ASSERT(!("Packets written out of order"));
	    goto exit;
	}

	if (pAinfo->uAudioStreamType == STREAMING_AUDIO)
	{
	    /* is it a resonable packet to add to the list */
	    if ((llActualTimestamp <= llActualLastEndTime		&&
		 llActualLastEndTime - llActualTimestamp <= m_ulFudge)	|| 
		(llActualTimestamp >= llActualLastEndTime		&&
		 llActualTimestamp - llActualLastEndTime <= m_ulFudge))
	    {

                // XXX wschildbach
                // make 64-bit timestamps contiguous before adding into the queue.
                pAinfo->llEndTimeInSamples   += pInfo->llEndTimeInSamples - pAinfo->llStartTimeInSamples ;
                pAinfo->llStartTimeInSamples  = pInfo->llEndTimeInSamples ;

                m_pDataList->AddTail((void*) pAinfo);
                
	    }
	    else
	    {
		theErr = HXR_NONCONTIGUOUS_PACKET; //HX_LATE_PACKET;
		/* something is fu*#$#up... figure out what?*/
		HX_ASSERT(!("Streaming Audio: Non-Contigous Write"));
		m_bTobeTimed	= TRUE;
		goto exit;
	    }
	}
	else
	{
	    /* see if there is any overlap.. we do not allow any overlap */
	    if (llActualTimestamp < llActualLastEndTime &&
		llActualLastEndTime - llActualTimestamp > m_ulFudge)
	    {
		/* hmmm an overlapped packet */
		theErr = HXR_OVERLAPPED_PACKET;
		/* something is fu*#$#up... figure out what?*/
		HX_ASSERT(!("Timed Audio: Overlapping write"));
		m_bTobeTimed	= TRUE;
		goto exit;
	    }
	    else
	    {
		m_pDataList->AddTail((void*) pAinfo);
	    }
	}
    }

exit:
    if (theErr != HXR_OK && pAinfo)
    {
	pAinfo->pBuffer->Release();
	delete pAinfo;
    }

//{FILE* f1 = ::fopen("e:\\audio.txt", "a+"); ::fprintf(f1, "%lu\t%p\t%d\t%lu\t%lu\n", HX_GET_BETTERTICKCOUNT(), this, m_pDataList->GetCount(), pAudioData->ulAudioTime, (UINT32)m_llLastWriteTime);::fclose(f1);}

    return theErr;
}

HX_RESULT CHXAudioStream::ProcessInfo(void)
{
    HX_RESULT theErr = HXR_OK;

    m_ulSampleFrameSize = m_AudioFmt.uChannels * (m_AudioFmt.uBitsPerSample>>3) ;

    m_ulPendingAudioBytes = 0 ;

    // Calculate the number of bytes per granularity.

    // XXX wschildbach: These formulas are suspect. There is no feedback to the player
    // or audio session that this is what we assume for a size. I believe this is either
    // not needed or too complex.

    m_ulInputBytesPerGran = (ULONG32) 
		(((m_AudioFmt.uChannels * (m_AudioFmt.uBitsPerSample>>3) * m_AudioFmt.ulSamplesPerSec) 
				/ 1000.0) * m_ulGranularity);

    m_ulOutputBytesPerGran  = (ULONG32) 
		(((m_DeviceFmt.uChannels * (m_DeviceFmt.uBitsPerSample>>3) * m_DeviceFmt.ulSamplesPerSec) 
				/ 1000.0) * m_ulGranularity);

    // Make sure that number of bytes per granularity is an even number.
    m_ulInputBytesPerGran  -= m_ulInputBytesPerGran  % ((m_AudioFmt.uBitsPerSample>>3)*m_AudioFmt.uChannels);
    m_ulOutputBytesPerGran -= m_ulOutputBytesPerGran % ((m_DeviceFmt.uBitsPerSample>>3)*m_DeviceFmt.uChannels);

    if (!theErr)
    {
        // set up the mixing engine
        // XXX wschildbach
        theErr = m_pMixEngine->Init(m_AudioFmt.ulSamplesPerSec, m_DeviceFmt.ulSamplesPerSec, m_AudioFmt.uChannels, m_DeviceFmt.uChannels) ;
        if (SUCCEEDED(theErr))
            theErr = m_pMixEngine->SetSampleConverter(this) ;
        if (SUCCEEDED(theErr))
            theErr = m_pMixEngine->SetOutputBytesPerSample(m_DeviceFmt.uBitsPerSample / 8) ;
        // set the volume (somebody might have set it when we did not have an engine)
#ifdef HELIX_FEATURE_GAINTOOL
        if (SUCCEEDED(theErr))
            m_pMixEngine->SetVolume(m_pMixEngine->HXVolume2TenthOfDB(m_bMute ? HX_MIN_VOLUME : m_uVolume)) ;
#endif
    }

    if (!theErr)
    {
	m_bInited = TRUE;
	
	if (m_eState == E_STOPPED)
	{
	    m_eState = E_INITIALIZED;
	} 
    }

    /* Get the current player time to set the last write audio time 
     * If someone creates a stream mid presentation, we ask the player 
     * object for the current write time.
     */
    // set last write time to be the current playback time since
    // this is what other system components(i.e. renderers) based on 
    // fixed b#69847 - loss of push-down-worth of data =
    // m_Owner->GetLastAudioWriteTime() - m_Owner->GetCurrentPlayBackTime()
//    m_llLastWriteTime = m_Owner->GetCurrentPlayBackTime();

    // XXXRA: It is necessary to use last audio write time for any delayed
    // audio streams to work that do not involve any Pause/Rewind logic.
    // To cover the case where a source (and an audio stream) has been added
    // mid-playback by SMIL renderer which has a delay equivalent to the 
    // current playback time, it should result in a player rewind which should 
    // reset the lastaudiowrite time accordingly...so we should be able
    // to use m_Owner->GetLastAudioWriteTime() value in such a use case as well.
    // this change is required to fix PR 79161 and PR 69780.
    // Henry, PR 69847 (the reason for the earlier change) is still busted. 
    // so I am reverting this code to the original code. you will have 
    // to come up with a different fix for PR 69847 since this was clearly not 
    // the correct fix.
    m_llLastWriteTime = m_Owner->GetLastAudioWriteTime();
    m_pMixEngine->ResetTimeLineInMillis(m_llLastWriteTime) ;

    if (!theErr && m_bInited)
    {
	m_Owner->StreamInitialized(this);
    }

    return theErr;
}


/************************************************************************
 *  Method:
 *		IHXAudioStream::GetFormat
 *	Purpose:
 *  	Return the stream's audio format.
 */
HX_RESULT CHXAudioStream::GetFormat
( 
    HXAudioFormat*	pAudioFormat
)
{
    if (!m_bAudioFormatKnown)
    {
	return HXR_NOT_INITIALIZED;
    }

    pAudioFormat->uChannels       = m_AudioFmt.uChannels;
    pAudioFormat->uBitsPerSample  = m_AudioFmt.uBitsPerSample;
    pAudioFormat->ulSamplesPerSec = m_AudioFmt.ulSamplesPerSec;
    pAudioFormat->uMaxBlockSize   = m_AudioFmt.uMaxBlockSize;

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::Setup
 *	Purpose:
 *		This is called by the player's Setup method. At this
 *		time the audio device format is set and we can now
 *		set up the streams pre-mixing buffer. This buffer
 *		contains data that has been resampled to match the
 *		audio device format.
 */
HX_RESULT CHXAudioStream::Setup(
	HXAudioFormat*	pFormat
,	ULONG32		ulGranularity
)
{
    HX_RESULT theErr = HXR_OK;

    memcpy( &m_DeviceFmt, pFormat, sizeof(HXAudioFormat) );
    m_ulGranularity = ulGranularity;

    m_bSetupDone = TRUE;
    
    /* we have all the info now.. so setup the resampler */
    if (m_bAudioFormatKnown && !m_bInited)
    {
	theErr = ProcessInfo();
    }

    return theErr;
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::ResetStream
 *	Purpose:
 */
void CHXAudioStream::ResetStream()
{
    m_bInited		= FALSE;
    m_bCanBeRewound	= FALSE;
    m_bSetupDone	= FALSE;
    m_bAudioFormatKnown	= FALSE;
    m_bIsResumed	= FALSE;
    
    UnRegister();

    while (m_pAvailableBuffers && m_pAvailableBuffers->GetCount() > 0)
    {
	IHXBuffer* pBuffer = (IHXBuffer*) m_pAvailableBuffers->RemoveHead();
	HX_RELEASE(pBuffer);
    }

    HX_DELETE(m_pAvailableBuffers);

    // Delete all entries in the audio data list
    FlushBuffers();
    HX_DELETE(m_pDataList);
    HX_DELETE(m_pInstantaneousList);

    CleanupRAByToTs();
    HX_DELETE(m_pRAByToTsInList);
    HX_DELETE(m_pRAByToTsAdjustedList);

    HX_DELETE(m_pMixEngine);

    m_bGotHooks = FALSE;

    m_llLastWriteTime = 0;
    m_ulTSRollOver = 0;

    HX_RELEASE(m_pValues);

#if defined(HELIX_FEATURE_AUDIO_PREMIXHOOK)
    // Delete all entries in the pre-mix hook list.
    if ( m_PreMixHookMap.GetCount() > 0)
    {
        HXAudioHookInfo* h = 0;
        CHXMapPtrToPtr::Iterator lIter = m_PreMixHookMap.Begin();
        for (; lIter != m_PreMixHookMap.End(); ++lIter)
        {
            h = (HXAudioHookInfo*) (*lIter);
	    ProcessAudioHook(ACTION_REMOVE, h->pHook);
            h->pHook->Release();
            delete h;
        }
	
	m_PreMixHookMap.RemoveAll();
    }
#endif /* HELIX_FEATURE_AUDIO_PREMIXHOOK */

#ifdef HELIX_FEATURE_VOLUME
    if( m_pStreamVolume )
    {
        m_pStreamVolume->RemoveAdviseSink(this);
        m_pStreamVolume->Release();
        m_pStreamVolume=NULL;
    }
#endif    

    
    HX_DELETE(m_pInDataPtr);
    HX_DELETE(m_pOutDataPtr);

    if (m_DryNotificationMap && m_DryNotificationMap->GetCount() > 0)
    {
        IHXDryNotification* pDryNotification = 0;
        CHXMapPtrToPtr::Iterator lIter = m_DryNotificationMap->Begin();
        for (; lIter != m_DryNotificationMap->End(); ++lIter)
        {
            pDryNotification = (IHXDryNotification*) (*lIter);
            pDryNotification->Release();
        }
	
	m_DryNotificationMap->RemoveAll();
    }

    HX_RELEASE(m_pCrossFadeStream);
    HX_RELEASE(m_pCommonClassFactory);
#if defined(HELIX_FEATURE_PREFERENCES)
    HX_RELEASE(m_pPreferences);
#endif /* HELIX_FEATURE_PREFERENCES */
    HX_RELEASE(m_Owner);

    return;
}

HX_RESULT
CHXAudioStream::ProcessAudioHook(PROCESS_ACTION action, 
				 IHXAudioHook* pAudioHook)
{
    return HXR_OK;
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::InitHooks
 *	Purpose:
 *	Init any pre-mix hooks. Return TRUE if hooks exist else return
 *	FALSE.
 */
void CHXAudioStream::InitHooks()
{
#if defined(HELIX_FEATURE_AUDIO_PREMIXHOOK)
    /* Iterate thru the hook list and call the hook's OnInit().
     * If any of the hooks have disabled write set to TRUE, then
     * we will let this override any set to FALSE.
     */
    if ( m_PreMixHookMap.GetCount() > 0 )
    {
	HXAudioHookInfo* h = 0;
	CHXMapPtrToPtr::Iterator lIter = m_PreMixHookMap.Begin();
	for (; lIter != m_PreMixHookMap.End(); ++lIter)
	{
	    h = (HXAudioHookInfo*) (*lIter);
	    if (h->bIgnoreAudioData ||
		HXR_OK == ProcessAudioHook(ACTION_CHECK, h->pHook))
	    {
		h->pHook->OnInit( &m_AudioFmt );
	    }
	}
    }
#endif /* HELIX_FEATURE_AUDIO_PREMIXHOOK */

    m_bHooksInitialized = TRUE;
}

/************************************************************************
 *  Method:
 *		IHXAudioStream::ProcessHooks
 *	Purpose:
 */
HX_RESULT CHXAudioStream::ProcessHooks
( 
    HXAudioData*	pInData,
    HXAudioData*	pOutData
)
{
    HX_RESULT theErr = HXR_OK;

#if defined(HELIX_FEATURE_AUDIO_PREMIXHOOK)
    m_pInDataPtr->pData		= pInData->pData;
    m_pInDataPtr->pData->AddRef();
    m_pInDataPtr->ulAudioTime	= pInData->ulAudioTime;
    
    m_pOutDataPtr->pData	= NULL;
    m_pOutDataPtr->ulAudioTime	= pInData->ulAudioTime;

    m_pInDataPtr->uAudioStreamType    = pInData->uAudioStreamType;
    m_pOutDataPtr->uAudioStreamType   = pInData->uAudioStreamType;

    if ( m_PreMixHookMap.GetCount() > 0 )
    {
	HXAudioHookInfo* pPreMixHookInfo = 0;
	CHXMapPtrToPtr::Iterator lIter = m_PreMixHookMap.Begin();
	for (; !theErr && lIter != m_PreMixHookMap.End(); ++lIter)
	{
	    pPreMixHookInfo = (HXAudioHookInfo*) (*lIter);

	    if (HXR_OK == ProcessAudioHook(ACTION_CHECK, pPreMixHookInfo->pHook))
	    {
                // XXXHP, disable hooks when it doesn't support multi-channel
                if (m_AudioFmt.uChannels <= 2 || pPreMixHookInfo->bMultiChannelSupport)
                {
		    theErr = pPreMixHookInfo->pHook->OnBuffer( m_pInDataPtr, m_pOutDataPtr);

		    /* Check to see if renderer changed the buffer. If so, then
		     * make this output as input to the next Hook.
		     */
		    if (!theErr && m_pOutDataPtr->pData)
		    {
		        m_pInDataPtr->pData->Release();
		        m_pInDataPtr->pData	    = m_pOutDataPtr->pData;
		        m_pInDataPtr->ulAudioTime   = m_pOutDataPtr->ulAudioTime;

		        m_pOutDataPtr->pData	= 0;
		    }
                }
	    }
	    else if (pPreMixHookInfo->bIgnoreAudioData)
	    {
		IHXBuffer* pTempBuf = m_pInDataPtr->pData;
		m_pInDataPtr->pData = NULL;
		theErr = pPreMixHookInfo->pHook->OnBuffer( m_pInDataPtr, m_pOutDataPtr);
		m_pInDataPtr->pData = pTempBuf;
	    }
	}
    }

    /* Final output is always in InDataPtr*/
    pOutData->pData		= m_pInDataPtr->pData;
    pOutData->ulAudioTime	= m_pInDataPtr->ulAudioTime;

    pOutData->uAudioStreamType  = m_pInDataPtr->uAudioStreamType;
#endif /* HELIX_FEATURE_AUDIO_PREMIXHOOK */

    return theErr;
}

/************************************************************************
 *  Method:
 *	CHXAudioStream::MixIntoBuffer
 *  Purpose:
 *	Mix stream data into this pPlayerBuf.
 *
 */
HX_RESULT CHXAudioStream::MixIntoBuffer
(
    UCHAR*   pPlayerBuf,
    ULONG32  ulBufSize,
    ULONG32& ulBufTime,
    BOOL&    bIsMixBufferDirty,
    BOOL     bGetCrossFadeData
)
{
    HX_RESULT res = HXR_OK ;

    if (!m_bInited)
    {
	return HXR_NOT_INITIALIZED;
    }

    // bGetCrossFadeData should now be a thing of the past.
    HX_ASSERT(!bGetCrossFadeData) ;

//{FILE* f1 = ::fopen("c:\\temp\\rasync.txt", "a+"); ::fprintf(f1, "Call MixIntoBuffer: %lu\n", m_ulLastWriteTime);::fclose(f1);}
    /* If this is a *FROM* stream, we may have already mixed
     * data during cross-fade with *TO* stream
     */

    // update the outside world's sense of time.
    // XXX wschildbach: how to account for rollover?
    INT64 llNextMixTime = m_pMixEngine->GetNextMixTimeMillis();
    UINT32 ulLastWriteTime = INT64_TO_UINT32(llNextMixTime - CAST_TO_INT64(m_ulTSRollOver) * CAST_TO_INT64(MAX_UINT32));

    // In a surestream situation, ulBufTime is 0 on the first stream, and a time corresponding
    // to the granularity in the second stream. I don't know what this code tries to do.
    // XXX wschildbach
    if (ulBufTime < ulLastWriteTime)
    {
	ulBufTime = ulLastWriteTime;
    }

    /* If there are any DryNotifications and the data list is empty
     * we need to notify them so that they can write more data.
     *
     * If EnoughDataAvailable() returns FALSE, it will have updated
     * the input values to point at where the buffer needs to be filled.
     */

    INT64  llStartMix, llEndMix;

    m_pMixEngine->GetMixRange(ulBufSize, llStartMix, llEndMix) ;
    UINT32 nSamplesNeeded = INT64_TO_UINT32(llEndMix - llStartMix) ; // always fits into UINT32

    if (!EnoughDataAvailable(llStartMix, nSamplesNeeded))
    {
        // Check if the audio device is really empty ("ReallyNeedData") or if we are
        // just over-eagerly filling up the pre-roll. If the latter is the case,
        // return HXR_WOULD_BLOCK
	if (!bIsMixBufferDirty && !m_Owner->m_Owner->ReallyNeedData())
	{
	    return HXR_WOULD_BLOCK;
	}

        // renderer might pause playback if has no packets
	if (m_DryNotificationMap->GetCount() > 0)
	{
	    IHXDryNotification* pDryNotification = 0;
	    CHXMapPtrToPtr::Iterator lIter = m_DryNotificationMap->Begin();
	    for (; lIter != m_DryNotificationMap->End(); ++lIter)
	    {
		pDryNotification = (IHXDryNotification*) (*lIter);
                UINT64 streamTime = Samples2Ms(llStartMix, &m_AudioFmt) -
                    CAST_TO_INT64(m_ulTSRollOver)*CAST_TO_INT64(MAX_UINT32);
		HX_RESULT theErr = pDryNotification->OnDryNotification( INT64_TO_UINT32(streamTime),
                                                     INT64_TO_UINT32(Samples2Ms(nSamplesNeeded, &m_AudioFmt))) ;
                if( theErr == HXR_OUTOFMEMORY )
                {
                    return theErr;
                }
	    }

	    if (m_Owner->GetState() != E_PLAYING)
	    {
		return HXR_OK;
	    }
	}
    }

    // XXX wschildbach what does this do?
    m_Owner->DataInAudioDevice(TRUE);

    /*
    {FILE *f1 = fopen("c:\\temp\\mix.txt","a+");
     fprintf(f1,"\ncall MixIntoBuffer(%I64d, id=%ld)\n",(INT64)mixTimeInSamples,m_pMixEngine - (HXAudioSvcMixEngine*)0xdde198);
     fclose(f1);}
*/

    // this call does all the mixing.
    res = m_pMixEngine->MixIntoBuffer(pPlayerBuf, ulBufSize, bIsMixBufferDirty) ;
    if( m_wLastError == HXR_OUTOFMEMORY )
    {
        return m_wLastError;
    }
 
    if (FAILED(res))
        return res ; 

#if defined(HELIX_FEATURE_AUDIO_INACCURATESAMPLING)
    if( m_bRealAudioStream )
        MapFudgedTimestamps();
#endif    
    
    // update the inside world's sense of time.
    m_llLastWriteTime = m_pMixEngine->GetNextMixTimeMillis() ;

#if 0
    {FILE *f = fopen("c:\\temp\\incoming.txt","a+");
    fprintf(f,"mix %ld %I64d\n",ulBufTime,m_llLastWriteTime);fclose(f);}
#endif

    return HXR_OK;
}

/*
 * This is the callback function that m_pMixEngine->MixIntoBuffer() will call to read
 * new samples.
 */

BOOL CHXAudioStream::ConvertIntoBuffer(tAudioSample* buffer, UINT32 nSamples, INT64 llStartTimeInSamples)
{
    HXAudioInfo*    pInfo           = 0;
    LISTPOSITION    lp              = 0;
    INT32           nBytesPerSample = m_AudioFmt.uBitsPerSample>>3 ;
    BOOL            didMix          = FALSE ; // set to TRUE if we have packets in this time range
    BOOL            bPacketsAfterRange = FALSE;

    /*
    {FILE *f1 = fopen("c:\\temp\\mix.txt","a+");
     fprintf(f1," ConvertIntoBuffer(%I64d - %I64d, len=%ld)\n",(INT64)llStartTimeInSamples,(INT64)llStartTimeInSamples+nSamples,nSamples);
     fclose(f1);}
     */

    // there are two lists of packets here: timed audio and instantaneous audio.
    // We only look into the list for timed buffers -- Instantaneoue audio is ignored
    // (it never properly worked anyway, so support is discontinued).

    /* remove old packets. Old packets are packets that have an end time that is before
       our current mix time. */
    lp = m_pDataList->GetHeadPosition();
    while( lp )
    {
	LISTPOSITION lastlp = lp;
	pInfo	= (HXAudioInfo*) m_pDataList->GetNext(lp);

	if (pInfo->llEndTimeInSamples < llStartTimeInSamples)
	{
            /*
    {FILE *f1 = fopen("c:\\temp\\mix.txt","a+");
     fprintf(f1,"-- reaping packet (%I64d,%I64d)\n",
         pInfo->llStartTimeInSamples,pInfo->llEndTimeInSamples);
     fclose(f1);}
     */

	    FreeInfo(pInfo);
	    m_pDataList->RemoveAt(lastlp);
            if( m_wLastError == HXR_OUTOFMEMORY )
            {
                return FALSE;
            }
	}
        else // if monotonous and non-overlapping
            break ;
    }

    // now go through the entire list of packets, and look for overlap with the
    // convert buffer. Any packet with overlap will be at least partially converted
    // into the buffer.

    // If packets overlap, one packet will then take precedence over another -- not
    // much we can do about that.

    lp = m_pDataList->GetHeadPosition();
    while( lp )
    {
	pInfo	= (HXAudioInfo*) m_pDataList->GetNext(lp);

	if (pInfo->llStartTimeInSamples < llStartTimeInSamples + nSamples &&
            pInfo->llEndTimeInSamples > llStartTimeInSamples)
        {
            // This packet has some overlap with what we are converting.

            // if this is the first packet to be mixed into this buffer,
            // silence out the entire buffer. This is inefficient, but safe
            if (!didMix)
                CAudioSvcSampleConverter::silence(buffer, nSamples) ;

            didMix = TRUE ;

            INT32 nMixbufferOffset = 0;
            INT32 pastPacketStart = INT64_TO_INT32(llStartTimeInSamples - pInfo->llStartTimeInSamples) ;
            if (pastPacketStart < 0)
            {
                nMixbufferOffset = -pastPacketStart;
                pastPacketStart = 0 ;
            }

            INT32 nn = Bytes2Samples(pInfo->pBuffer->GetSize(), &m_AudioFmt) ;
            INT32 nSamplesToUse = nn - pastPacketStart;
            if (nSamplesToUse > (INT32)(nSamples - nMixbufferOffset))
                nSamplesToUse = nSamples - nMixbufferOffset;

            const unsigned char *cvtin = pInfo->pBuffer->GetBuffer() + pastPacketStart * nBytesPerSample ;
            tAudioSample *cvtout = buffer + nMixbufferOffset ;

            /*
    {FILE *f1 = fopen("c:\\temp\\mix.txt","a+");
     fprintf(f1,"  mix packet (%I64d,%I64d,len=%ld) into buffer(%ld,%ld,len=%ld)\n",
         pInfo->llStartTimeInSamples,pInfo->llEndTimeInSamples,
         nn,
         nMixbufferOffset,nMixbufferOffset+nSamplesToUse,nSamplesToUse);
     fprintf(f1,"  pastPacketStart = %I64d, nMixbufferOffset = %ld\n",
                pastPacketStart,nMixbufferOffset) ;
     fclose(f1);}
*/
            switch (nBytesPerSample)
            {
            case 1:
                CAudioSvcSampleConverter::cvt8(cvtin, cvtout, nSamplesToUse);
                break ;
            case 2:
                CAudioSvcSampleConverter::cvt16(cvtin, cvtout, nSamplesToUse);
                break ;
            case 4:
                CAudioSvcSampleConverter::cvt32(cvtin, cvtout, nSamplesToUse);
                break ;
            }
        }
	else if (pInfo->llStartTimeInSamples >= llStartTimeInSamples + nSamples)
	{
	    /* We've found audio data that is past the
	     * desired range.
	     */
	    bPacketsAfterRange = TRUE;
	}
    }

    if (!didMix && bPacketsAfterRange)
    {
	/* We do not have packets for this range, but we
	 * do have packets after the range.
	 * Create silence data and make it look like we
	 * actually had data for this range.
	 */
	CAudioSvcSampleConverter::silence(buffer, nSamples) ;
	didMix = TRUE;
    }

    return didMix ;
}





/************************************************************************
 *  Method:
 *              CHXAudioStream::Bytes2Samples
 *      Purpose:
 *		Translate from units of bytes to samples.
 */
UINT32 CHXAudioStream::Bytes2Samples 
(
    UINT64	ulNumBytes,
    const HXAudioFormat *fmt 
)
{
    ASSERT(ulNumBytes % (fmt->uBitsPerSample >> 3) == 0) ;
    return INT64_TO_UINT32(ulNumBytes / (fmt->uBitsPerSample >> 3)) ;
}

/************************************************************************
 *  Method:
 *              CHXAudioStream::Samples2Ms
 *      Purpose:
 *		Calculate the duration in millisecs for this number of samples.
 */
UINT64 CHXAudioStream::Samples2Ms
(
    INT64 nSamples,
    const HXAudioFormat *fmt
)
{
    UINT32 ulDenom = fmt->uChannels * fmt->ulSamplesPerSec;
    UINT64 q = nSamples / ulDenom;
    UINT64 r = nSamples - q * ulDenom;

    return q * 1000 + (r * 1000) / ulDenom;
}


/************************************************************************
 *  Method:
 *              CHXAudioStream::CalcMs
 *      Purpose:
 *		Calculate the duration in millisecs for this number of
 *              bytes in input format.
 */
ULONG32 CHXAudioStream::CalcMs
(
    ULONG32	ulNumBytes
)
{
    return INT64_TO_ULONG32(Samples2Ms(Bytes2Samples(ulNumBytes, &m_AudioFmt), &m_AudioFmt));
}

/************************************************************************
 *  Method:
 *              CHXAudioStream::CalcDeviceMs
 *      Purpose:
 *		Calculate the duration in millisecs for this number of 
 *		bytes in Device format.
 */
ULONG32 CHXAudioStream::CalcDeviceMs
(
    ULONG32	ulNumBytes
)
{
    return INT64_TO_ULONG32(Samples2Ms(Bytes2Samples(ulNumBytes, &m_DeviceFmt), &m_DeviceFmt));
}

/************************************************************************
 *  Method:
 *              CHXAudioStream::CalcOffset
 *      Purpose:
 *		Calculate the offset in bytes given time.
 */
UINT32 CHXAudioStream::CalcOffset
(
    INT64 llStartTime
,   INT64 llEndTime
)
{
    /* Using m_ulBytesPerMs may introduce cumulative error due 
     * to decimal cutoff 
     */
    HX_ASSERT(llEndTime - llStartTime < MAX_TIMESTAMP_GAP);

    return m_ulGranularity ?
        INT64_TO_UINT32((llEndTime - llStartTime) * m_ulInputBytesPerGran / m_ulGranularity) :
        0 ;
}


void CHXAudioStream::FlushBuffers(BOOL bInstantaneousAlso)
{
    while (m_pDataList && m_pDataList->GetCount() > 0)
    {
	HXAudioInfo* pInfo = (HXAudioInfo*) m_pDataList->RemoveHead();
	FreeInfo(pInfo);
    }

    while (bInstantaneousAlso && m_pInstantaneousList && m_pInstantaneousList->GetCount() > 0)
    {
	CHXSimpleList* pList = (CHXSimpleList*) m_pInstantaneousList->RemoveHead();
	while (pList->GetCount() > 0)
	{
	    HXAudioInfo* pInfo = (HXAudioInfo*) pList->RemoveHead();
	    FreeInfo(pInfo, TRUE);
	}

	HX_DELETE(pList);
    }

    // reset m_bLastNMilliSecsToBeSaved so that we actually 
    // delete buffers in FreeInfo
    BOOL bLastNMilliSecsToBeSaved = m_bLastNMilliSecsToBeSaved;
    m_bLastNMilliSecsToBeSaved = FALSE;

    while (m_pLastNMilliSecsList && m_pLastNMilliSecsList->GetCount() > 0)
    {
	HXAudioInfo* pInfo = (HXAudioInfo*) m_pLastNMilliSecsList->RemoveHead();
	FreeInfo(pInfo);
    }

    m_bLastNMilliSecsToBeSaved = bLastNMilliSecsToBeSaved;

    HX_DELETE(m_pLastNMilliSecsList);
}

/*
    this routine checks if there are enough packets waiting in the queue
    to be mixed. It will return FALSE if not, or if there are packets missing
    in the middle of the queue.
*/

BOOL
CHXAudioStream::EnoughDataAvailable(INT64& llStartTimeInSamples, UINT32& nSamplesRequired)
{
    INT64           llEndTimeInSamples   = llStartTimeInSamples + nSamplesRequired ;

    HXAudioInfo*    pInfoOld        = 0 ;
    HXAudioInfo*    pInfo           = 0;
    LISTPOSITION    lp              = 0;

    // if the list is completely empty, report the whole data range as missing
    if (m_pDataList->IsEmpty())
	return FALSE ;

    nSamplesRequired = 0 ;

    /* skip over old packets. Old packets are packets that have an end time that is before
       our current mix time. */
    lp = m_pDataList->GetHeadPosition();
    while( lp )
    {
	pInfoOld = (HXAudioInfo*) m_pDataList->GetNext(lp);

	if (pInfoOld->llEndTimeInSamples >= llStartTimeInSamples)
            break ;
    }

#if 0 // disabled missing packet detection
    // pInfoOld is the first packet to be mixed. To make sure it overlaps with the start
    // of the mix buffer, do this (disabled for now):
    
    if (pInfoOld->llStartTimeInSamples > llStartTimeInSamples)
        return FALSE ;
#endif    

    // now go through the rest of packets, and make sure they are contiguous until
    // the end of our mix time

    // If packets overlap, one packet will then take precedence over another -- not
    // much we can do about that.

    while( lp )
    {
	pInfo    = (HXAudioInfo*) m_pDataList->GetNext(lp);

        // if we see a packet with a timestamp after the mix time ("future packet")
        // or one that does not abut with the previous one ("discontinuity"), stop.
        if (pInfo->llStartTimeInSamples >= llEndTimeInSamples)         // future packet
//            pInfo->llStartTimeInSamples != pInfoOld->llEndTimeInSamples) // discontinuity
        {
            break ;
        }

        pInfoOld = pInfo ;
    }

    // pInfoOld is the last packet to be mixed (or the last before a discontinuity).
    // Make sure it overlaps with the end of the mix buffer.
    if (pInfoOld->llEndTimeInSamples < llEndTimeInSamples)
    {
        llStartTimeInSamples = pInfoOld->llEndTimeInSamples ;
        nSamplesRequired = INT64_TO_UINT32(llEndTimeInSamples - llStartTimeInSamples) ;
        return FALSE ;
    }

    return TRUE ; // Data available!
}


HX_RESULT    
CHXAudioStream::StartCrossFade(CHXAudioStream*  pFromStream, 
			       UINT32		ulCrossFadeStartTime,
			       UINT32		ulCrossFadeDuration, 
			       BOOL		bToStream)
{
#if defined(HELIX_FEATURE_CROSSFADE)
    // XXX wschildbach need to account for rollover.
    INT64 llStartTimeInSamples = CAST_TO_INT64(ulCrossFadeStartTime) * m_DeviceFmt.ulSamplesPerSec / 1000 * m_DeviceFmt.uChannels ;
    INT64 llEndTimeInSamples = (CAST_TO_INT64(ulCrossFadeStartTime)+ulCrossFadeDuration) * m_DeviceFmt.ulSamplesPerSec / 1000 * m_DeviceFmt.uChannels ;

    m_pMixEngine->SetCrossFade(bToStream ? HXAudioSvcMixEngine::FADE_IN : HXAudioSvcMixEngine::FADE_OUT,
        llStartTimeInSamples, llEndTimeInSamples) ;
/*
    {
        FILE *f2 = fopen("c:\\temp\\mix.txt","a+");
        fprintf(f2,"** StartCrossFade(%I64d, %I64d, len=%ld, to=%s\n",
            llStartTimeInSamples,
            llEndTimeInSamples,
            (INT32)(-llStartTimeInSamples+llEndTimeInSamples),
            bToStream?"yes":"no");
        fclose(f2);
    }
*/

    return HXR_OK;
#else
    return HXR_NOTIMPL;
#endif /* HELIX_FEATURE_CROSSFADE */
}

/*
 *  IHXRealAudioSync methods
 */

/************************************************************************
 *  Method:
 *      IHXRealAudioSync::Register
 *  Purpose:
 */
STDMETHODIMP
CHXAudioStream::Register(void) 
{
#if defined _DEBUG && defined HELIX_FEATURE_AUDIO_MULTIPLAYER_PAUSE 
    if (HXDebugOptionEnabled("zDoNotUseFudge"))
    {
        return HXR_OK;
    }
#endif

    if (m_bRealAudioStream)
    {
	return HXR_UNEXPECTED;
    }

    m_bRealAudioStream = TRUE;
    m_Owner->RegisterRealAudioStream(this);

#if defined(HELIX_FEATURE_AUDIO_INACCURATESAMPLING)
    if (!m_pRAByToTsInList)
    {
	m_pRAByToTsInList	= new CHXSimpleList;
	m_pRAByToTsAdjustedList	= new CHXSimpleList;
    }
#endif /* HELIX_FEATURE_AUDIO_INACCURATESAMPLING */

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *      IHXRealAudioSync::UnRegister
 *  Purpose:
 */
STDMETHODIMP
CHXAudioStream::UnRegister(void)
{
#if defined _DEBUG && defined HELIX_FEATURE_AUDIO_MULTIPLAYER_PAUSE 
    if (HXDebugOptionEnabled("zDoNotUseFudge"))
    {
        return HXR_OK;
    }
#endif

    if (!m_bRealAudioStream)
    {
	return HXR_UNEXPECTED;
    }

    m_bRealAudioStream = FALSE;
    m_Owner->UnRegisterRealAudioStream(this);

    CleanupRAByToTs();

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *      IHXRealAudioSync::FudgeTimestamp
 *  Purpose:
 *	Tell the audio stream about the relationship between the number 
 *	of bytes written to the actual timestamp.
 *	    
 */
STDMETHODIMP
CHXAudioStream::FudgeTimestamp(UINT32 /*IN*/ ulNumberofBytes,
			       UINT32 /*IN*/ ulTimestamp)
{
#if defined(HELIX_FEATURE_AUDIO_INACCURATESAMPLING)
#if defined _DEBUG && defined HELIX_FEATURE_AUDIO_MULTIPLAYER_PAUSE 
    if (HXDebugOptionEnabled("zDoNotUseFudge"))
    {
        return HXR_OK;
    }
#endif

    RealAudioBytesToTimeStamp* pByToTs = 
	new RealAudioBytesToTimeStamp;

    pByToTs->m_ulTimestamp	= ulTimestamp;
    pByToTs->m_ulInTimestamp	= m_ulLastInputStartTime;
    pByToTs->m_ulInEndTime	= m_ulLastInputEndTime;

    if (m_bIsLive && m_ulBaseTime > 0)
    {
	pByToTs->m_ulTimestamp	+= m_ulLiveDelay;

	if (pByToTs->m_ulTimestamp > m_ulBaseTime)
	{
	    pByToTs->m_ulTimestamp -= m_ulBaseTime;
	}
	else
	{
	    pByToTs->m_ulTimestamp  = 0;
	}
    }

    pByToTs->m_ulOrigTimestamp	= pByToTs->m_ulTimestamp;

    m_pRAByToTsInList->AddTail((void*) pByToTs);
#endif /* HELIX_FEATURE_AUDIO_INACCURATESAMPLING */

//{FILE* f1 = ::fopen("d:\\temp\\audio.txt", "a+"); ::fprintf(f1, "Fudge:\t%lu\t%lu\n", ulTimestamp, m_ulLastInputStartTime);::fclose(f1);}
    return HXR_OK;
}

void
CHXAudioStream::CleanupRAByToTs(void)
{
#if defined(HELIX_FEATURE_AUDIO_INACCURATESAMPLING)
    if (!m_pRAByToTsInList)
    {
	return;
    }

    CHXSimpleList::Iterator ndx = m_pRAByToTsInList->Begin();
    for (; ndx != m_pRAByToTsInList->End(); ++ndx)
    {
	RealAudioBytesToTimeStamp* pByToTs = 
	    (RealAudioBytesToTimeStamp*) (*ndx);
	delete pByToTs;
    }

    m_pRAByToTsInList->RemoveAll();

    ndx = m_pRAByToTsAdjustedList->Begin();
    for (; ndx != m_pRAByToTsAdjustedList->End(); ++ndx)
    {
	RealAudioBytesToTimeStamp* pByToTs = 
	    (RealAudioBytesToTimeStamp*) (*ndx);
	delete pByToTs;
    }

    m_pRAByToTsAdjustedList->RemoveAll();
#endif /* HELIX_FEATURE_AUDIO_INACCURATESAMPLING */
} 

HX_RESULT
CHXAudioStream::ConvertCurrentTime(double dBytesPlayed, 
				   UINT32 ulCurrentTime, 
				   UINT32& ulAdjustedTime)
{
#if defined(HELIX_FEATURE_AUDIO_INACCURATESAMPLING)
    HX_ASSERT(m_bRealAudioStream);

    ulAdjustedTime  = ulCurrentTime;

    LISTPOSITION posRABytes = m_pRAByToTsAdjustedList->GetHeadPosition();
    RealAudioBytesToTimeStamp* pByToTsLower = NULL;
    RealAudioBytesToTimeStamp* pByToTsHigher = NULL;

    INT64   llActualByToTsHigherTimestamp = 0;
    INT64   llActualByToTsLowerTimestamp =0;

    while(posRABytes)
    {
	RealAudioBytesToTimeStamp* pByToTs = (RealAudioBytesToTimeStamp*) 
			    m_pRAByToTsAdjustedList->GetAt(posRABytes);

	if (dBytesPlayed >= pByToTs->m_ulOutNumBytes)
	{
	    pByToTsLower = pByToTs;
	}
	else
	{
	    if (pByToTsLower)
	    {
		pByToTsHigher = pByToTs; 
	    }
	    else
	    {
		/* It means that this stream was added mid-presentation and we have not yet 
		 * played any bits from this stream. Maintain the current time and do not
		 * fudge it.
		 */ 
		return HXR_OK;
	    }
	}

	if (pByToTsLower && pByToTsHigher)
	{
	    break;
	}

	m_pRAByToTsAdjustedList->GetNext(posRABytes);
    }

    /* We got a range, interpolate */
    if (pByToTsLower && pByToTsHigher)
    {
//{FILE* f1 = ::fopen("d:\\temp\\rasync.txt", "a+"); ::fprintf(f1, "ConvertLowHigh: dBytesPlayed: %f LowTS: %lu HighTS: %lu LowBytes: %f HighBytes: %f\n", dBytesPlayed,pByToTsLower->m_ulTimestamp,pByToTsHigher->m_ulTimestamp, pByToTsLower->m_ulOutNumBytes,pByToTsHigher->m_ulOutNumBytes);::fclose(f1);}

	/* Need to re-visit this ASSERT. A check will do for now */
#if 0
	HX_ASSERT((pByToTsHigher->m_ulTimestamp >= 
		   pByToTsLower->m_ulTimestamp) &&
		  (pByToTsHigher->m_ulOutNumBytes >= 
		   pByToTsLower->m_ulOutNumBytes));
#endif

	llActualByToTsHigherTimestamp = CAST_TO_INT64 (pByToTsHigher->m_ulTimestamp) + CAST_TO_INT64 m_ulTSRollOver * CAST_TO_INT64 MAX_UINT32;
	llActualByToTsLowerTimestamp = CAST_TO_INT64 (pByToTsLower->m_ulTimestamp) + CAST_TO_INT64 m_ulTSRollOver * CAST_TO_INT64 MAX_UINT32;

	if ((llActualByToTsHigherTimestamp >= llActualByToTsLowerTimestamp) &&
	    (pByToTsHigher->m_ulOutNumBytes >= pByToTsLower->m_ulOutNumBytes))
	{
	    ulAdjustedTime = pByToTsLower->m_ulTimestamp +
			 (UINT32) (((dBytesPlayed - pByToTsLower->m_ulOutNumBytes)*1./
			 (pByToTsHigher->m_ulOutNumBytes - 
			  pByToTsLower->m_ulOutNumBytes)) *
			  INT64_TO_UINT32(llActualByToTsHigherTimestamp - 
			  llActualByToTsLowerTimestamp));
//{FILE* f1 = ::fopen("d:\\temp\\multi.txt", "a+"); ::fprintf(f1, "ConvertLHINTER: %p %p\t%lu\t%lu\t%lu\t\t%lu\t%lu\t%lu\t%lu\n", this, m_Owner, (UINT32) dBytesPlayed, ulCurrentTime, ulAdjustedTime, pByToTsLower->m_ulTimestamp, (UINT32) pByToTsLower->m_ulOutNumBytes, pByToTsHigher->m_ulTimestamp, (UINT32) pByToTsHigher->m_ulOutNumBytes);::fclose(f1);}
	}
	else 
	{
	    ulAdjustedTime = pByToTsLower->m_ulTimestamp;
//{FILE* f1 = ::fopen("d:\\temp\\multi.txt", "a+"); ::fprintf(f1, "ConvertLH: %p %p\t%lu\t%lu\t%lu\n", this, m_Owner, (UINT32) dBytesPlayed, ulCurrentTime, ulAdjustedTime);::fclose(f1);}
	}
//{FILE* f1 = ::fopen("d:\\temp\\rasync.txt", "a+"); ::fprintf(f1, "ConvertLowHigh: ulCurrentTime: %lu ulAdjustedTime: %lu dBytesPlayed: %f LowTS: %lu HighTS: %lu\n", ulCurrentTime, ulAdjustedTime, dBytesPlayed,pByToTsLower->m_ulTimestamp,pByToTsHigher->m_ulTimestamp);::fclose(f1);}
    }
    /* The best we can do is return the time of the nearest map */
    else if (pByToTsLower)
    {
	ulAdjustedTime = pByToTsLower->m_ulTimestamp;
	double dBytesDiff = dBytesPlayed - pByToTsLower->m_ulOutNumBytes;

	if (dBytesDiff > 0)
	{
	    double dNumBytes = m_Owner->ConvertMsToBytes(pByToTsLower->m_ulDuration);
	    if (dBytesDiff >= dNumBytes)
	    {
		ulAdjustedTime += pByToTsLower->m_ulDuration;
	    }
	    else
	    {
		ulAdjustedTime += (UINT32) (pByToTsLower->m_ulDuration * dBytesDiff *1./dNumBytes);
	    }
	}
//{FILE* f1 = ::fopen("d:\\temp\\rasync.txt", "a+"); ::fprintf(f1, "ConvertLower: ulCurrentTime: %lu ulAdjustedTime: %lu dBytesPlayed: %f LowTS: %lu m_ulOutNumBytes: %f\n", ulCurrentTime, ulAdjustedTime, dBytesPlayed,pByToTsLower->m_ulTimestamp, pByToTsLower->m_ulOutNumBytes);::fclose(f1);}
    }

    /* Remove all maps before pByToTsLower */
    posRABytes = m_pRAByToTsAdjustedList->GetHeadPosition();
    while(posRABytes)
    {
	RealAudioBytesToTimeStamp* pByToTs = 
	    (RealAudioBytesToTimeStamp*) m_pRAByToTsAdjustedList->GetAt(posRABytes);

	if (pByToTs != pByToTsLower)
	{
//{FILE* f1 = ::fopen("d:\\temp\\rasync.txt", "a+"); ::fprintf(f1, "Delete: OutBytes: %f OutTS: %lu\n", pByToTs->m_ulOutNumBytes, pByToTs->m_ulTimestamp);::fclose(f1);}
	    delete pByToTs;
	    posRABytes = m_pRAByToTsAdjustedList->RemoveAt(posRABytes);
	}
	else
	{
	    break;
	}
    }
#else
    ulAdjustedTime = ulCurrentTime;
#endif /* HELIX_FEATURE_AUDIO_INACCURATESAMPLING */
//{FILE* f1 = ::fopen("d:\\temp\\multi.txt", "a+"); ::fprintf(f1, "Convert: %p %p\t%lu\t%lu\t%lu\n", this, m_Owner, (UINT32) dBytesPlayed, ulCurrentTime, ulAdjustedTime);::fclose(f1);}
    return HXR_OK;
}

void
CHXAudioStream::MapFudgedTimestamps(void)
{
#if defined(HELIX_FEATURE_AUDIO_INACCURATESAMPLING)
    LISTPOSITION    posRABytes = m_pRAByToTsInList->GetHeadPosition();
    INT64	    llActualByToTsInEndTime = 0;
    INT64	    llActualByToTsInStartTime = 0;
    
    while(posRABytes)
    {
	RealAudioBytesToTimeStamp* pByToTs = 
	    (RealAudioBytesToTimeStamp*) m_pRAByToTsInList->GetAt(posRABytes);

	llActualByToTsInStartTime = CAST_TO_INT64 (pByToTs->m_ulInTimestamp) + CAST_TO_INT64 m_ulTSRollOver * CAST_TO_INT64 MAX_UINT32;
	llActualByToTsInEndTime = CAST_TO_INT64 (pByToTs->m_ulInEndTime) + CAST_TO_INT64 m_ulTSRollOver * CAST_TO_INT64 MAX_UINT32;

	/* Too late */
	if (llActualByToTsInEndTime < m_llLastWriteTime)
	{
	    posRABytes = m_pRAByToTsInList->RemoveAt(posRABytes);
	    delete pByToTs;
	}
	else if (llActualByToTsInStartTime <= m_llLastWriteTime
		/*&& pByToTs->m_ulInEndTime >= m_ulLastStartTimePlayed*/)
	{
	    /* These two values will be used in determining what time it is */

	    // Number of bytes that have been written to the audio device till now*/
	    pByToTs->m_ulOutNumBytes = m_Owner->NumberOfBytesWritten();
 
	    HX_ASSERT(m_llLastWriteTime - llActualByToTsInStartTime < MAX_TIMESTAMP_GAP);

	    /* Interpolate */
	    UINT32 ulTimeDiff = INT64_TO_UINT32(m_llLastWriteTime - llActualByToTsInStartTime);
	    pByToTs->m_ulTimestamp += ulTimeDiff;
	    
	    pByToTs->m_ulDuration   = INT64_TO_UINT32((llActualByToTsInEndTime - 
				       llActualByToTsInStartTime) - CAST_TO_INT64 ulTimeDiff);
				      
	    posRABytes = m_pRAByToTsInList->RemoveAt(posRABytes);
	    m_pRAByToTsAdjustedList->AddTail(pByToTs);

//{FILE* f1 = ::fopen("d:\\temp\\multi.txt", "a+"); ::fprintf(f1, "Map: %p %p\t%lu\t%lu\t%lu\t%lu\t%lu\t\t%lu\n", this, m_Owner, (UINT32) pByToTs->m_ulOutNumBytes, pByToTs->m_ulInTimestamp, pByToTs->m_ulInEndTime, pByToTs->m_ulTimestamp, pByToTs->m_ulOrigTimestamp, m_llLastWriteTime);::fclose(f1);}
	}
	else
	{
	    break;
	}
    }
#endif /* HELIX_FEATURE_AUDIO_INACCURATESAMPLING */
}

// XXX wschildbach: How to implement this with the 64-bit timestamps?

void
CHXAudioStream::UpdateStreamLastWriteTime(BOOL bForceUpdate /*= FALSE*/)
{
    if (m_bLastWriteTimeUpdated)
    {
	return;
    }

    m_bLastWriteTimeUpdated = TRUE;

    if (m_bIsLive)
    {
	if (!m_pValues || m_pValues->GetPropertyULONG32("LiveSyncStartTime", m_ulBaseTime) != HXR_OK)
	{
	    if (bForceUpdate)
	    {
		m_bIsLive = FALSE;
		m_ulBaseTime = 0;
		m_llLastWriteTime = m_Owner->GetLastAudioWriteTime();
	    }
	    else
	    {
		/* 
		 * do not set it yet.. we will wait till the first
		 * AddData call 
		 */
		m_bLastWriteTimeUpdated = FALSE;
	    }
	}
	else
	{
	    m_pValues->GetPropertyULONG32("Delay", m_ulLiveDelay);
	    INT64 llLastPlayerWriteTime = m_Owner->GetLastAudioWriteTime();

	    if (m_ulLiveDelay > 0 && 
		CAST_TO_INT64  m_ulLiveDelay > llLastPlayerWriteTime &&
		m_ulBaseTime > INT64_TO_UINT32(CAST_TO_INT64  m_ulLiveDelay-llLastPlayerWriteTime))
	    {
		m_llLastWriteTime   = CAST_TO_INT64 (m_ulBaseTime - 
					INT64_TO_UINT32(CAST_TO_INT64  m_ulLiveDelay-llLastPlayerWriteTime));
	    }
	    else
	    {
		m_llLastWriteTime   = CAST_TO_INT64  m_ulBaseTime;
	    }
	}
    }
    else
    {
	// XXX HP
	//
	// Prolbem: 
	// when rewinding audio data upon resume, the audio-push-down worth of data
	// would be missing when the stream's first resume is at the middle of playback,
	// 
	// Solution: 
	// we need to adjust the m_llLastWriteTime to the last audio player write time
	if (m_Owner->IsResumed() && !m_bIsResumed)
	{
	    m_llLastWriteTime = m_Owner->GetLastAudioWriteTime();
	}
    }
    if (m_bLastWriteTimeUpdated && m_pMixEngine)
        m_pMixEngine->ResetTimeLineInMillis(m_llLastWriteTime) ;
}

void
CHXAudioStream::SaveLastNMilliSeconds(BOOL bSave, UINT32 ulNMilliSeconds)
{
    m_bLastNMilliSecsToBeSaved	= bSave; // TRUE;	//
    m_ulLastNMilliSeconds	= ulNMilliSeconds; // 2000;//

    HX_ASSERT(!m_bLastNMilliSecsToBeSaved || m_ulLastNMilliSeconds > 0);

    // ensure we need to save for atleast 1 sec
    if (m_bLastNMilliSecsToBeSaved && m_ulLastNMilliSeconds < 1000)
    {
	m_ulLastNMilliSeconds = 1000; 
    }

    if (!m_bLastNMilliSecsToBeSaved)
    {
	while (m_pLastNMilliSecsList && m_pLastNMilliSecsList->GetCount() > 0)
	{
	    HXAudioInfo* pInfo = (HXAudioInfo*) m_pLastNMilliSecsList->RemoveHead();
	    FreeInfo(pInfo);
	}

	HX_DELETE(m_pLastNMilliSecsList);
    }
}

void
CHXAudioStream::RewindStream(UINT32 ulTimeToRewind)
{
    HX_ASSERT(m_bLastNMilliSecsToBeSaved);

    if (!m_bCanBeRewound)
    {
	return;
    }

    if (m_bLastNMilliSecsToBeSaved && m_pLastNMilliSecsList)
    {
	HX_ASSERT(m_llLastWriteTime >= ulTimeToRewind);
	if (m_llLastWriteTime >= ulTimeToRewind)
	{
	    m_llLastWriteTime -= ulTimeToRewind;
	}
	else
	{
	    m_llLastWriteTime = 0;
	}

	HXAudioInfo* pInfo = NULL;

	// reset any pInfo's in data list that may have been partially used.
	CHXSimpleList::Iterator ndx = m_pDataList->Begin();
	for (; ndx != m_pDataList->End(); ++ndx)
	{
	    pInfo = (HXAudioInfo*) (*ndx);
	    if (pInfo->ulBytesLeft  != pInfo->pBuffer->GetSize())
	    {
		pInfo->pOffset	    = pInfo->pBuffer->GetBuffer();
		pInfo->ulBytesLeft  = pInfo->pBuffer->GetSize();
	    }
	    else
	    {
		break;
	    }
	}

	UINT32 ulLastWriteTime = INT64_TO_UINT32(m_llLastWriteTime - CAST_TO_INT64 m_ulTSRollOver * CAST_TO_INT64 MAX_UINT32);
	BOOL bTimedToBeSet = (m_pLastNMilliSecsList->GetCount() > 0);

	while (m_pLastNMilliSecsList->GetCount() > 0)
	{
	    pInfo = (HXAudioInfo*) m_pLastNMilliSecsList->RemoveTail();
	    m_pDataList->AddHead(pInfo);

	    if (pInfo->ulStartTime <= ulLastWriteTime)
	    {
		break;
	    }
	}

	if (bTimedToBeSet)
	{
	    pInfo = (HXAudioInfo*) m_pDataList->GetHead();
	    pInfo->uAudioStreamType = TIMED_AUDIO;
	}

	// remove remaining elements from the list
	while (m_pLastNMilliSecsList->GetCount() > 0)
	{
	    pInfo = (HXAudioInfo*) m_pLastNMilliSecsList->RemoveHead();
	    // delete the stored one
	    HX_RELEASE(pInfo->pBuffer);
	    HX_DELETE(pInfo);
	}

//{FILE* f1 = ::fopen("d:\\temp\\multi.txt", "a+"); ::fprintf(f1, "%p %p RewindStream %lu %lu\n", this, m_Owner, ulFirstAudioTime, m_llLastWriteTime);::fclose(f1);}

#if defined(HELIX_FEATURE_AUDIO_INACCURATESAMPLING)
	// put back stuff from adjusted list to in list
	while (m_pRAByToTsAdjustedList && m_pRAByToTsAdjustedList->GetCount() > 0)
	{
	    RealAudioBytesToTimeStamp* pByToTs = 
		(RealAudioBytesToTimeStamp*) m_pRAByToTsAdjustedList->RemoveTail();
	    // restore original fudge timestamp
	    pByToTs->m_ulTimestamp = pByToTs->m_ulOrigTimestamp;
	    m_pRAByToTsInList->AddHead(pByToTs);
	}
#endif /* HELIX_FEATURE_AUDIO_INACCURATESAMPLING */
    }
}

void
CHXAudioStream::Pause(BOOL bPlayerPause)
{
    if (m_eState == E_PAUSED)
    {
	return;
    }

    m_eState = E_PAUSED;
    m_bCanBeRewound = FALSE;
    m_bPlayerPause = bPlayerPause;

    return;
}

void
CHXAudioStream::Resume(BOOL bPlayerResume)
{
    if (!m_bInited ||
	m_eState == E_PLAYING)
    {
	return;
    }

    UpdateStreamLastWriteTime();
    // resetting mixengine time line is done in UpdateStreamLastWriteTime()

    // add/resume audio stream on the fly
    if( m_Owner->IsResumed() )
    {
        
        if (m_eState != E_PAUSED	&&
            !m_bIsResumed		&& 
            (!m_pDataList->IsEmpty() || !m_pInstantaneousList->IsEmpty()))
        {
            m_Owner->AudioStreamStateChanged(E_PLAYING);
            m_eState = E_PLAYING;
        }
        // whoever pause the stream is responsible for resuming the same
        // stream, the stream can either be paused specifically by the SMIL renderer
        // without pausing the playback or be paused by the AudioPlayer which
        // pauses the playback
        else if (!bPlayerResume || m_bPlayerPause)
        {
            m_eState = E_PLAYING;
        }
    }
    else
    {
        m_eState = E_PLAYING;
    }
    
    if (m_eState == E_PLAYING)
    {
	m_bCanBeRewound = TRUE;       
	m_bIsResumed = TRUE;
    }
 
    return;
}

void
CHXAudioStream::Seek(UINT32 ulSeekTime)
{
    m_llLastWriteTime	= CAST_TO_INT64 (m_ulBaseTime + ulSeekTime);

    // XXX wschildbach: How to account for rollover?
    m_pMixEngine->ResetTimeLineInMillis(m_llLastWriteTime) ;

    m_bFirstWrite	= TRUE;
    m_bTobeTimed	= TRUE;
    m_ulTSRollOver	= 0;
    m_ulLastInputStartTime  = 0;
    m_ulLastInputEndTime    = 0;
    // Remove all buffers from auxlliary list. This means that a 
    // renderer must send all buffers again including those buffers
    // that start at time t way out there in the future.
    FlushBuffers();
    CleanupRAByToTs();
    
    /* Remove any cross-fading */
    m_bCrossFadingToBeDone  = FALSE;
    HX_RELEASE(m_pCrossFadeStream);

    /* clear the pending bytes buffer */
    m_ulPendingAudioBytes = 0 ;
    HX_RELEASE(m_piPendingAudioData) ;

    // XXX HP what happen if this is called from the client core on
    //	      IHXTrack::Seek()
    return;
}

void
CHXAudioStream::Stop(void)
{
    if (m_eState == E_STOPPED)
    {
	return;
    }

    m_eState = E_STOPPED;
    ResetStream();

    return;
}

void	
CHXAudioStream::SetAudioDeviceReflushHint(BOOL bSupported)
{
    m_bAudioDeviceReflushHint = bSupported;
    return;
}

void
CHXAudioStream::FreeInfo(HXAudioInfo* pInfo, BOOL bInstantaneous /* = FALSE */)
{
    if (m_bLastNMilliSecsToBeSaved && !bInstantaneous)
    {
	if (!m_pLastNMilliSecsList)
	{
	    m_pLastNMilliSecsList	= new CHXSimpleList;
	    m_ulLastNHeadTime	= pInfo->ulStartTime;
	    m_ulLastNTailTime	= pInfo->ulStartTime;
	}

	// reset members
	pInfo->pOffset	    = pInfo->pBuffer->GetBuffer();
	pInfo->ulBytesLeft  = pInfo->pBuffer->GetSize();

	// add it to the tail
	m_pLastNMilliSecsList->AddTail(pInfo);

        // Last m_ulLastNTailTime could have been invalidated by a rewind:
        // check it again here:
        m_ulLastNHeadTime = ((HXAudioInfo*) m_pLastNMilliSecsList->GetHead())->ulStartTime;
	m_ulLastNTailTime = pInfo->ulStartTime;

	// time to expire certain blocks?
	if (CALCULATE_ELAPSED_TICKS(m_ulLastNHeadTime, m_ulLastNTailTime) > m_ulLastNMilliSeconds)
	{
	    // override pInfo. we will delete this block at the bottom
	    pInfo = (HXAudioInfo*) m_pLastNMilliSecsList->RemoveHead();

	    // update head time
	    HXAudioInfo* pHeadInfo = (HXAudioInfo*) m_pLastNMilliSecsList->GetHead();

	    // we should always have ATLEAST one nore in the list
	    HX_ASSERT(pHeadInfo);
	    m_ulLastNHeadTime = pHeadInfo->ulStartTime;
	}
	else
	{
	    // early exit to save this block
	    return;
	}
    }

    FreeBuffer(pInfo->pBuffer);
    delete pInfo;
}

void
CHXAudioStream::FreeBuffer(IHXBuffer* pBuffer)
{
    /* do we need to keep it around for reuse? */
    if (!m_pAvailableBuffers || m_pAvailableBuffers->GetCount() >= m_uCacheSize)
    {
//{FILE* f1 = ::fopen("d:\\temp\\cache.txt", "a+"); ::fprintf(f1, "Discard \n");::fclose(f1);}
	/* 
	 * now that we are full, we should grow the cache size, if we ever have
	 * a cache miss
	 */
	m_bCacheMayBeGrown = TRUE;
	pBuffer->Release();
	return;
    }

    /* 
     * check if we have the only reference, if so reuse it
     * else release our reference
     */
    pBuffer->AddRef();
    if (pBuffer->Release() > 1)
    {
	pBuffer->Release();
	return;
    }

#ifdef _MACINTOSH
    m_pAvailableBuffers->AddTail((void*) pBuffer);
#else
    BOOL bAddToTail = (HX_GET_BETTERTICKCOUNT() & 0x01) ? TRUE : FALSE;

    LISTPOSITION listRet = NULL;
    if (bAddToTail)
    {
	listRet = m_pAvailableBuffers->AddTail((void*) pBuffer);
    }
    else
    {
	listRet = m_pAvailableBuffers->AddHead((void*) pBuffer);
    }
    if( listRet == NULL )
    {
        m_wLastError = HXR_OUTOFMEMORY;
    }
#endif


//{FILE* f1 = ::fopen("d:\\temp\\cache.txt", "a+"); ::fprintf(f1, "Added %d\n", m_pAvailableBuffers->GetCount());::fclose(f1);}
    return;
}

/************************************************************************
 *  Method:
 *	IHXCommonClassFactory::CreateInstance
 */
STDMETHODIMP 
CHXAudioStream::CreateInstance
(
    REFCLSID	/*IN*/		rclsid,
    void**	/*OUT*/		ppUnknown
)
{
    HX_RESULT theErr = HXR_OK;

    if (IsEqualCLSID(rclsid, CLSID_IHXBuffer))
    {
	if (!m_pAvailableBuffers)
	{
#ifdef _MACINTOSH
	    m_pAvailableBuffers = new HXAudioMacQueue;
#else
	    m_pAvailableBuffers = new CHXSimpleList;
#endif
	}

	if (m_pAvailableBuffers->GetCount() > 0)
	{
#ifdef _MACINTOSH
	    *ppUnknown = (IUnknown*) (IHXBuffer*) m_pAvailableBuffers->RemoveHead();
	    if (!*ppUnknown) goto justincase;
#else
	    BOOL bRemoveFromHead = (HX_GET_BETTERTICKCOUNT() & 0x01) ? TRUE : FALSE;

	    if (bRemoveFromHead)
	    {
		*ppUnknown = (IUnknown*) (IHXBuffer*) m_pAvailableBuffers->RemoveHead();
	    }
	    else
	    {
		*ppUnknown = (IUnknown*) (IHXBuffer*) m_pAvailableBuffers->RemoveTail();
	    }
#endif

//{FILE* f1 = ::fopen("d:\\temp\\cache.txt", "a+"); ::fprintf(f1, "Cache Hit %d\n", m_pAvailableBuffers->GetCount());::fclose(f1);}
	    goto exit;
	}
	else
	{
#ifdef _MACINTOSH
justincase:
#endif
	    if (m_bCacheMayBeGrown)
	    {
		m_bCacheMayBeGrown  = FALSE;
		m_uCacheSize	    += CACHE_INCREMENT_SIZE;
//{FILE* f1 = ::fopen("d:\\temp\\cache.txt", "a+"); ::fprintf(f1, "Cache increased to: %u \n", m_uCacheSize);::fclose(f1);}
	    }
	}
	/* 
	 * fall down to using the comonclass factory to allocate this buiffer since 
	 * we do not have it in the cache
	 */
    }

//{FILE* f1 = ::fopen("d:\\temp\\cache.txt", "a+"); ::fprintf(f1, "Cache Miss buffered blocks: %d\n", m_pDataList->GetCount());::fclose(f1);}
    theErr = m_pCommonClassFactory->CreateInstance(rclsid, ppUnknown);
exit:
    return theErr;
}

/************************************************************************
 *  Method:
 *	IHXCommonClassFactory::CreateInstanceAggregatable
 */
STDMETHODIMP 
CHXAudioStream::CreateInstanceAggregatable
(
    REFCLSID	    /*IN*/	rclsid,
    REF(IUnknown*)  /*OUT*/	pUnknown,
    IUnknown*	    /*IN*/	pUnkOuter
)
{
    return m_pCommonClassFactory->CreateInstanceAggregatable(rclsid, pUnknown, pUnkOuter);
}

// XXX wschildbach: What does this method do? How do we implement this with
// the 64-bit timestamps?

/************************************************************************
 *	Method:
 *	    IHXUpdateProperties::UpdatePacketTimeOffset
 *	Purpose:
 *	    Call this method to update the timestamp offset of cached packets
 */
STDMETHODIMP
CHXAudioStream::UpdatePacketTimeOffset(INT32 lTimeOffset)
{
    HX_RESULT	    rc = HXR_OK;
    HXAudioInfo*    pInfo = NULL;

    // adjust the start time 
    CHXSimpleList::Iterator ndx = m_pDataList->Begin();
    for (; ndx != m_pDataList->End(); ++ndx)
    {
	pInfo = (HXAudioInfo*) (*ndx);
	pInfo->ulStartTime += lTimeOffset;
    }
  
    if (m_pLastNMilliSecsList)
    {
	ndx = m_pLastNMilliSecsList->Begin();
	for (; ndx != m_pLastNMilliSecsList->End(); ++ndx)
	{
	    pInfo = (HXAudioInfo*) (*ndx);
	    pInfo->ulStartTime += lTimeOffset;
	}
    }

    // Adjust more state:
    m_ulLastInputStartTime += lTimeOffset;
    m_ulLastInputEndTime += lTimeOffset;  
    m_llLastWriteTime += lTimeOffset;
    m_ulLastNHeadTime += lTimeOffset;  
    m_ulLastNTailTime += lTimeOffset;  
    m_llLastStartTimePlayed += lTimeOffset;

    return rc;
}

/************************************************************************
 *	Method:
 *	    IHXUpdateProperties::UpdatePlayTimes
 *	Purpose:
 *	    Call this method to update the playtime attributes
 */
STDMETHODIMP
CHXAudioStream::UpdatePlayTimes(IHXValues* pProps)
{
    return HXR_OK;
}

// XXX wschildbach: How do we implement this method with 64-bit timestamps?
void
CHXAudioStream::RollBackTimestamp()
{
    if (m_llLastWriteTime > CAST_TO_INT64 m_ulGranularity)
    {
	m_llLastWriteTime -= CAST_TO_INT64 m_ulGranularity;
    }
}

#ifdef _MACINTOSH

/////////////////////////////////////////////////////////////////////////
//	
//	HXAudioMacQueue
//	
//	For passing data between an interrupt and anything else (mac only).
//
HXAudioMacQueue::HXAudioMacQueue()
{
 	mQueueHeader.qFlags=0;	
	mQueueHeader.qHead=0;
	mQueueHeader.qTail=0;
	mDestructing = FALSE; // just a safety check
	m_nCount = 0;
}

/////////////////////////////////////////////////////////////////////////
//
HX_RESULT HXAudioMacQueue::AddTail(void* pObject)
{
	if (pObject && !mDestructing)
	{
	    HXAudioMacQueueElement * theElement = new HXAudioMacQueueElement();  
	    
	    if (theElement)
	    {  
		    theElement->mNextElementInQueue = NULL;
		    theElement->mObject = pObject;    
		    ::Enqueue((QElem *)theElement, &mQueueHeader);
		    
		    m_nCount++;
		    
		    //
		    // If someone interrupts and enters the destructor while we're in here,
		    // then the pObject and the new node will be leaked.  This shouldn't 
		    // happen since we should have shut down all interrupts that would
		    // be adding items to the queue long before we start destructing it.
		    //
		    
		    HX_ASSERT(!mDestructing); // if we DID enter the destructor, let the programmer know...
	    }
	    	    
	    return HXR_OK;
    }
        
    return HXR_FAILED;
}

/////////////////////////////////////////////////////////////////////////
//
void * HXAudioMacQueue::RemoveHead()
{
	//
    // POINT A
    // 
    // You can look at the qHead anytime you want, but you can't USE a
    // pointer unless it's OFF of the queue.  Basically you do a
    // Dequeue, and if it succeeds then you know nobody else has it.
    // If it fails, an error is returned and you don't mess with it.
    //
    
	if (mQueueHeader.qHead)
    {
 		HXAudioMacQueueElement * theElement = (HXAudioMacQueueElement *) mQueueHeader.qHead;

		if (theElement)
		{			
			OSErr e = ::Dequeue( (QElemPtr) theElement, &mQueueHeader );
			
			//
			// Between points A and D, we can't be
			// guaranteed that the queue header and
			// theElement are valid.  But Dequeue will
			// TELL us if that pointer is still valid by
			// its return code.  If it can't remove the
			// item from the queue, then somebody else did
			// and the pointer is no longer ours.  If no
			// error was returned from dequeue, then it's
			// ours to mess with.
			//
						
			if (e == noErr)
			{
				// at this point we know that we can
				// do whatever we need to with the
				// object.
				void* theObj = theElement->mObject;
				delete theElement; // delete the node
				m_nCount--;
				HX_ASSERT(m_nCount >= 0);
				return theObj;
			}
		}
   }
   
   return NULL;
}

/////////////////////////////////////////////////////////////////////////
//
UINT32 HXAudioMacQueue::GetCount()
{
    return m_nCount;
}

/////////////////////////////////////////////////////////////////////////
//
HXAudioMacQueue::~HXAudioMacQueue()
{
	mDestructing = TRUE; // don't add anything else to the queue
	
	void * theObject;

	while ((theObject = RemoveHead()) != 0)
    	{
    	}
    
    // and just to be safe...
	mQueueHeader.qHead=0;
	mQueueHeader.qTail=0;
}

#endif
