/* ***** BEGIN LICENSE BLOCK ***** 
 * Version: RCSL 1.0/RPSL 1.0 
 *  
 * Portions Copyright (c) 1995-2002 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 
 * Version 1.0 (the "RPSL") available at 
 * http://www.helixcommunity.org/content/rpsl unless you have licensed 
 * the file under the RealNetworks Community Source License Version 1.0 
 * (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.  
 *  
 * 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 "pcmrendr.ver"

#include "hxcom.h"
#include <stdio.h>
#ifndef _WINCE
#include <signal.h>
#endif

#include "hxtypes.h"
#include "hxcomm.h"
#include "audhead.h"

#include "legacy.h"  //has AudioPCMHEADER class.

#include "ihxpckts.h"
#include "hxfiles.h"
#include "hxcore.h"
#include "hxengin.h"
#include "hxprefs.h"
#include "hxrendr.h"
#include "hxplugn.h"
#include "hxausvc.h"
#include "hxbuffer.h"
#include "pcmrendr.h"
#include "netbyte.h"
#include "hxver.h"
#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static char HX_THIS_FILE[] = __FILE__;
#endif

#define DRY_BUFFER_COUNT 1

HX_RESULT STDAPICALLTYPE CPCMRenderer::HXCreateInstance
(
    IUnknown**  /*OUT*/	ppIUnknown
)
{
    *ppIUnknown = (IUnknown*)(IHXPlugin*)new CPCMRenderer();
    if (*ppIUnknown)
    {
	(*ppIUnknown)->AddRef();
	return HXR_OK;
    }
    return HXR_OUTOFMEMORY;
}

HX_RESULT STDAPICALLTYPE CPCMRenderer::CanUnload2(void)
{
    return ((CHXBaseCountingObject::ObjectsActive() > 0) ? HXR_FAIL : HXR_OK);
}

const char* CPCMRenderer::zm_pDescription    = "Helix PCM Renderer Plugin";
const char* CPCMRenderer::zm_pCopyright      = HXVER_COPYRIGHT;
const char* CPCMRenderer::zm_pMoreInfoURL    = HXVER_MOREINFO;

const char* CPCMRenderer::zm_pStreamMimeTypes[] = {"audio/x-pn-wav", 
						   NULL};

CPCMRenderer::CPCMRenderer()
	: m_lRefCount(0)
	, m_pContext(NULL)
        , m_pStream(NULL)
        , m_pPlayer(NULL)
        , m_pHeader(NULL)
        , m_pLastPacket(NULL)
        , m_ulLastTime(0)
	, m_lTimeOffset(0)
	, m_ulTrackStartTime(0)
	, m_ulTrackEndTime(0)
	, m_bFirstPacket(TRUE)
#ifdef DEBUG_LOG
	, m_pLogFile(NULL)
#endif
	, m_pcmfile(0)
	, m_pAudioPlayer(0)
	, m_pAudioStream(0)
	, m_pCommonClassFactory(0)
	, m_bSwapSampleBytes (FALSE)
	, m_bZeroOffsetPCM (FALSE)
	, m_bIsGapInStreaming(TRUE)
	, m_bInSeekMode(FALSE)
	, m_ulFudge(5)
	, m_ulSamplesPerSecond(0)
	, m_ulLastEndTime(0)
	, m_bEightBitsPerSample(TRUE)
	, m_bIsStereo(FALSE)
	, m_bStreamDry(FALSE)
	, m_bPacketsDone(FALSE)
	, m_chBufferingCount(0)
{ 
}


CPCMRenderer::~CPCMRenderer() 
{ 
    EndStream();
};


/************************************************************************
 *  Method:
 *    IHXPlugin::InitPlugin
 *  Purpose:
 *    Initializes the plugin for use. This interface must always be
 *    called before any other method is called. This is primarily needed 
 *    so that the plugin can have access to the context for creation of
 *    IHXBuffers and IMalloc.
 */
STDMETHODIMP CPCMRenderer::InitPlugin(IUnknown* /*IN*/ pContext)
{
    m_pContext = pContext;
    m_pContext->AddRef();

    if (HXR_OK !=
	m_pContext->QueryInterface(IID_IHXCommonClassFactory, (void**)&m_pCommonClassFactory))
    {
	return HXR_INVALID_PARAMETER;
    }

#ifdef DEBUG_LOG
    m_pLogFile = fopen("pcmrendr.log", "w");
    if(!m_pLogFile)
    {
	m_pLogFile = stdout;
    }
    fprintf(m_pLogFile, "CPCMRenderer::InitPlugin()\n");
    fflush(m_pLogFile);
#endif

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetPluginInfo
 *  Purpose:
 *    Returns the basic information about this plugin. Including:
 *
 *    bLoadMultiple	whether or not this plugin DLL can be loaded
 *			multiple times. All File Formats must set
 *			this value to TRUE.
 *    pDescription	which is used in about UIs (can be NULL)
 *    pCopyright	which is used in about UIs (can be NULL)
 *    pMoreInfoURL	which is used in about UIs (can be NULL)
 */
STDMETHODIMP CPCMRenderer::GetPluginInfo
(
    REF(BOOL)        /*OUT*/ bLoadMultiple,
    REF(const char*) /*OUT*/ pDescription,
    REF(const char*) /*OUT*/ pCopyright,
    REF(const char*) /*OUT*/ pMoreInfoURL,
    REF(ULONG32)     /*OUT*/ ulVersionNumber
)
{
    bLoadMultiple = TRUE;   // Must be true for file formats.

    pDescription    = zm_pDescription;
    pCopyright	    = zm_pCopyright;
    pMoreInfoURL    = zm_pMoreInfoURL;
    ulVersionNumber = TARVER_ULONG32_VERSION;

    return HXR_OK;
}

/************************************************************************
 *  Method:
 *    IHXPlugin::GetRendererInfo
 *  Purpose:
 *    If this object is a file format object this method returns
 *    information vital to the instantiation of file format plugins.
 *    If this object is not a file format object, it should return
 *    HXR_UNEXPECTED.
 */
STDMETHODIMP CPCMRenderer::GetRendererInfo
(
    REF(const char**) /*OUT*/ pStreamMimeTypes,
    REF(UINT32)      /*OUT*/ unInitialGranularity
)
{
    pStreamMimeTypes = zm_pStreamMimeTypes;
    unInitialGranularity = 50;  // OnTimeSync() every 50ms (this is the same as
				// the TIME_PER_PACKET in wvffplin.cpp
    m_ulGranularity = unInitialGranularity;
    return HXR_OK;
}

// *** IUnknown methods ***

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::QueryInterface
//  Purpose:
//	Implement this to export the interfaces supported by your 
//	object.
//
STDMETHODIMP CPCMRenderer::QueryInterface(REFIID riid, void** ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown))
    {
	AddRef();
	*ppvObj = this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXPlugin))
    {
	AddRef();
	*ppvObj = (IHXPlugin*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXRenderer))
    {
	AddRef();
	*ppvObj = (IHXRenderer*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXInterruptSafe))
    {
	AddRef();
	*ppvObj = (IHXInterruptSafe*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXDryNotification))
    {
	AddRef();
	*ppvObj = (IHXDryNotification*)this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IHXUpdateProperties))
    {
	AddRef();
	*ppvObj = (IHXUpdateProperties*)this;
	return HXR_OK;
    }

    *ppvObj = NULL;
    return HXR_NOINTERFACE;
}

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

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

    delete this;
    return 0;
}

// *** IHXRenderer methods ***

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::StartStream
//  Purpose:
//	Called by client engine to inform the renderer of the stream it
//	will be rendering. The stream interface can provide access to
//	its source or player. This method also provides access to the 
//	primary client controller interface.
//
STDMETHODIMP CPCMRenderer::StartStream
(
    IHXStream*	    pStream,
    IHXPlayer*	    pPlayer
)
{
    // Save for later use!
    m_pStream  = pStream;
    m_pPlayer  = pPlayer;

    if (m_pStream ) m_pStream->AddRef();
    if (m_pPlayer ) m_pPlayer->AddRef();

    // get interface to audio player
    if ( HXR_OK != m_pPlayer->QueryInterface(IID_IHXAudioPlayer, (void**) &m_pAudioPlayer ))
    {
        return !HXR_OK;
    }

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::EndStream
//  Purpose:
//	Called by client engine to inform the renderer that the stream
//	is was rendering is closed.
//
STDMETHODIMP CPCMRenderer::EndStream()
{
    // We're done with these...
    if (m_pHeader) m_pHeader->Release();	    m_pHeader	   = NULL;
    if (m_pLastPacket) m_pLastPacket->Release();    m_pLastPacket  = NULL;
    if (m_pContext) m_pContext->Release();	    m_pContext	   = NULL;
    if (m_pStream) m_pStream->Release();	    m_pStream	   = NULL;
    if (m_pPlayer) m_pPlayer->Release();	    m_pPlayer	   = NULL;
    if (m_pAudioPlayer) m_pAudioPlayer->Release();  m_pAudioPlayer = NULL;
    if (m_pAudioStream) m_pAudioStream->Release();  m_pAudioStream = NULL;
    if (m_pCommonClassFactory) m_pCommonClassFactory->Release(); m_pCommonClassFactory = NULL;
#ifdef DEBUG_LOG
    if (m_pLogFile)
    {
	fprintf(m_pLogFile, "CPCMRenderer::EndStream()\n");
	fflush(m_pLogFile);
	fclose (m_pLogFile);
	m_pLogFile = NULL;
    }
#endif

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnHeader
//  Purpose:
//	Called by client engine when a header for this renderer is 
//	available. The header will arrive before any packets.
//
STDMETHODIMP CPCMRenderer::OnHeader(IHXValues* pHeader)
{
    // wave format stuff
    PCMHEADER* pPcmFormatRecord;
    PCMHEADER  tmpPcmFormatRecord;

    // Keep this for later use...
    m_pHeader = pHeader;
    m_pHeader->AddRef();

    IHXBuffer* pOpaqueData = 0;
    ULONG32 ulStreamNumber = 0;
    ULONG32 ulMaxBitRate = 0;
    ULONG32 ulAvgBitRate = 0;
    ULONG32 ulMaxPacketSize = 0;
    ULONG32 ulAvgPacketSize = 0;
    ULONG32 ulStartTime = 0;
    ULONG32 ulPreroll = 0;
    ULONG32 ulDuration = 0;
    IHXBuffer* pStreamName = 0;
    IHXBuffer* pMimeType = 0;

    pHeader->GetPropertyBuffer ("OpaqueData",       pOpaqueData);
    pHeader->GetPropertyULONG32("StreamNumber",     ulStreamNumber);
    pHeader->GetPropertyULONG32("MaxBitRate",       ulMaxBitRate);
    pHeader->GetPropertyULONG32("AvgBitRate",       ulAvgBitRate);
    pHeader->GetPropertyULONG32("MaxPacketSize",    ulMaxPacketSize);
    pHeader->GetPropertyULONG32("AvgPacketSize",    ulAvgPacketSize);
    pHeader->GetPropertyULONG32("StartTime",        ulStartTime);
    pHeader->GetPropertyULONG32("Preroll",          ulPreroll);
    pHeader->GetPropertyULONG32("Duration",         ulDuration);
    pHeader->GetPropertyCString("StreamName",       pStreamName);
    pHeader->GetPropertyCString("MimeType",         pMimeType);
        
#ifdef DEBUG_LOG
    fprintf(m_pLogFile, "CPCMRenderer::OnHeader()\n"
	    "OpaqueData length: %lu\n"
	    "StreamNumber: %lu\n"
	    "MaxBitRate: %lu\n"
	    "AvgBitRate: %lu\n"
	    "MaxPacketSize: %lu\n"
	    "AvgPacketSize: %lu\n"
	    "StartTime: %lu\n"
	    "Preroll: %lu\n"
	    "Duration: %lu\n"
	    "StreamName: %s\n"
	    "MimeType: %s\n",
	    pOpaqueData?pOpaqueData->GetSize():0L, ulStreamNumber,
	    ulMaxBitRate, ulAvgBitRate, ulMaxPacketSize, ulAvgPacketSize,
	    ulStartTime, ulPreroll, ulDuration, pStreamName, pMimeType);
    fflush(m_pLogFile);
#endif

    if (HXR_OK != pHeader->GetPropertyULONG32("TrackStartTime", m_ulTrackStartTime))
    {
	m_ulTrackStartTime = 0;
    }

    if (HXR_OK != pHeader->GetPropertyULONG32("TrackEndTime", m_ulTrackEndTime))
    {
	m_ulTrackEndTime = ulDuration;
    }
    
    BOOL bBigEndian = TestBigEndian(); //False if Intel

    AudioPCMHEADER audioPCMHEADER;
    BOOL bIsOldHeader = FALSE;
    if(pOpaqueData->GetSize() != audioPCMHEADER.static_size())
    {
	//don't attempt to touch the bytes beyond the end of the buffer:
	bIsOldHeader = TRUE;
    }
    else
    {
	audioPCMHEADER.unpack(pOpaqueData->GetBuffer(), pOpaqueData->GetSize());
    }
    //Check if this is from an old (preview release of G2 / rmasdk beta-9
    // or earlier) file format:
    if(bIsOldHeader  ||
	    audioPCMHEADER.usVersion != 0  ||
	    audioPCMHEADER.usMagicNumberTag !=
	    AUDIO_PCMHEADER_MAGIC_NUMBER)
    {
	pPcmFormatRecord = (PCMHEADER*) pOpaqueData->GetBuffer();

	//Make sure endianness is ok by swapping bytes if and only if
	// usChannels is > 0xff; don't just count on this being net-
	// endian as some preview-release file formats did not send
	// the data net-endian:
	if(pPcmFormatRecord->usChannels > 0xff)
	{
    	    SwapWordBytes((UINT16*)&pPcmFormatRecord->usFormatTag, 1);
    	    SwapWordBytes((UINT16*)&pPcmFormatRecord->usChannels, 1);
    	    SwapDWordBytes((UINT32*)&pPcmFormatRecord->ulSamplesPerSec, 1);
    	    SwapWordBytes((UINT16*)&pPcmFormatRecord->usBitsPerSample, 1);
    	    SwapWordBytes((UINT16*)&pPcmFormatRecord->usSampleEndianness, 1);
	}
    }
    else //is valid pAudioPCMHEADER and is already in the correct byte-order
	// as performed by audioPCMHEADER.unpack():
    {
	pPcmFormatRecord = &tmpPcmFormatRecord;
	switch(audioPCMHEADER.usVersion)
	{
	    case 0:
	    {
		//PCM format stuff starts 4 bytes into the buffer (which is
		// right after audioPCMHEADER.usVersion and
		// audioPCMHEADER.usMagicNumberTag:
    		pPcmFormatRecord->usFormatTag = audioPCMHEADER.usFormatTag;
    		pPcmFormatRecord->usChannels = audioPCMHEADER.usChannels;
    		pPcmFormatRecord->ulSamplesPerSec =
			audioPCMHEADER.ulSamplesPerSec;
    		pPcmFormatRecord->usBitsPerSample =
			audioPCMHEADER.usBitsPerSample;
    		pPcmFormatRecord->usSampleEndianness =
			audioPCMHEADER.usSampleEndianness;
		break;
	    }
	    default:
		HX_RELEASE(pOpaqueData);
		HX_RELEASE(pStreamName);
		HX_RELEASE(pMimeType);
		return HXR_UNEXPECTED;
	}
    }


    if (pPcmFormatRecord->usFormatTag == PN_PCM_ZERO_OFFSET &&
	pPcmFormatRecord->usBitsPerSample == 8)
    {
	m_bZeroOffsetPCM = TRUE;
    }

    if (pPcmFormatRecord->usBitsPerSample == 16 &&
	pPcmFormatRecord->usSampleEndianness != bBigEndian) 
    {
	m_bSwapSampleBytes = TRUE;
    }

    m_audioFmt.uChannels 	= pPcmFormatRecord->usChannels;
    m_audioFmt.uBitsPerSample 	= pPcmFormatRecord->usBitsPerSample;
    m_audioFmt.ulSamplesPerSec 	= pPcmFormatRecord->ulSamplesPerSec;
    m_audioFmt.uMaxBlockSize  	= (UINT16)ulMaxPacketSize;

    m_pAudioPlayer->CreateAudioStream( &m_pAudioStream );
    m_pAudioStream->Init( &m_audioFmt, pHeader);
    m_pAudioStream->AddDryNotification((IHXDryNotification*)this);

    // save it for later use
    m_ulSamplesPerSecond    = pPcmFormatRecord->ulSamplesPerSec;
    m_bEightBitsPerSample   = 8 == pPcmFormatRecord->usBitsPerSample;
    m_bIsStereo		    = 2 == pPcmFormatRecord->usChannels;

    // set up initial time
    m_ulCurrentTime = 0;

    if (pOpaqueData)	pOpaqueData->Release();
    if (pStreamName)	pStreamName->Release();
    if (pMimeType)	pMimeType->Release();

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPacket
//  Purpose:
//	Called by client engine when a packet for this renderer is 
//	due.  We write to Audio Services whenever we get a packet.
//	AS takes care of our buffereing for us.
//
STDMETHODIMP CPCMRenderer::OnPacket(IHXPacket* pPacket, LONG32	lTimeOffset)
{

    if (m_bInSeekMode)
    {
	return HXR_OK;
    }

    if (m_bFirstPacket)
    {
	m_lTimeOffset = lTimeOffset;
	m_bFirstPacket = FALSE;
    }
    
    // did we get a valid packet?
    if (pPacket->IsLost())
    {
#ifdef DEBUG_LOG
	fprintf(m_pLogFile, "CPCMRenderer::OnPacket(); packet was lost.\n");
	fflush(m_pLogFile);
#endif

	m_bIsGapInStreaming = TRUE;
	return HXR_OK;
    }

    if (m_bStreamDry)
    {
	m_chBufferingCount++;
	// now we have one more packet!
	m_pStream->ReportRebufferStatus(DRY_BUFFER_COUNT, m_chBufferingCount);
	if (m_chBufferingCount == DRY_BUFFER_COUNT)
	{
// {FILE* f1 = ::fopen("c:\\aurend.txt", "a+"); ::fprintf(f1, "***ReBuffering Done tickCount: %11lu\n", HX_GET_BETTERTICKCOUNT());::fclose(f1);}
	    m_bStreamDry = FALSE;   
	    m_chBufferingCount = 0;
	    m_bIsGapInStreaming = TRUE;
	}
    }

    IHXBuffer* pBuffer = 0;
    pBuffer = pPacket->GetBuffer();
    
#ifdef DEBUG_LOG
    ULONG32 ulPacketSize = 0;
    if(pBuffer)
    {
	ulPacketSize = pBuffer->GetSize();
    }
    fprintf(m_pLogFile, "CPCMRenderer::OnPacket(); packet time = %lu"
	    "(lTimeOffset: %ld), size = %lu\n",
	    pPacket->GetTime(), lTimeOffset, ulPacketSize);
    fflush(m_pLogFile);
#endif

    /* subtract the offset from the packet time before sending to Audio services */
    ULONG32 ulRealTime = pPacket->GetTime(); 
    
    IHXBuffer* pNewBuffer = 0;
    /* hmmm.. we will modify data, so need to make a copy since
     * we are not allowed to modify an existing COM object since it
     * might be in use by other parts of the system 
     * (e.g. one more PCM renderer)
     */
    if (m_bSwapSampleBytes || m_bZeroOffsetPCM)
    {
	 if (HXR_OK != 
	     m_pCommonClassFactory->CreateInstance(CLSID_IHXBuffer, (void**) &pNewBuffer))
	 {
	     return HXR_OUTOFMEMORY;
	 }

	 pNewBuffer->Set(pBuffer->GetBuffer(), pBuffer->GetSize());
    }
    else
    {
	pNewBuffer = pBuffer;
	pNewBuffer->AddRef();
    }

    UINT32 len = pNewBuffer->GetSize();

    // byte swap the samples if necessary
    if (m_bSwapSampleBytes)
    {
	UCHAR *pSampleBuf;
//	UINT32 len;
	pNewBuffer->Get(pSampleBuf, len);
    	SwapWordBytes((UINT16 *)pSampleBuf, len/2);
    }

    // convert to two's complement if necessary (only used for 8-bit samples)
    if (m_bZeroOffsetPCM)
    {
	UCHAR *pSampleBuf;
//	UINT32 len;
	pNewBuffer->Get(pSampleBuf, len);

	for (UINT32 i = 0; i < len; i++)
	{
	    pSampleBuf[i] -= 128;
	}
    }

    // Write to Audio Services
    HXAudioData  audioData;
    audioData.pData = pNewBuffer;
    audioData.ulAudioTime = ulRealTime; 

    // A packet is missing because expected packet has never been sent
    // so the sequence # is correct, but the time stamp is not.
    // Need to reset the audio service.
    if ((ulRealTime >=  m_ulLastEndTime && ulRealTime - m_ulLastEndTime > m_ulFudge) ||
	(ulRealTime <=  m_ulLastEndTime && m_ulLastEndTime - ulRealTime > m_ulFudge))
    {
	m_bIsGapInStreaming = TRUE;
    }
    
    // figure out the end time to be used for the next packet
    if(!m_bEightBitsPerSample)
    {
	// 16 bits/sample
	len /= 2;
    }
    if(m_bIsStereo)
    {
	len /= 2;
    }
    m_ulLastEndTime = (1000 * len) / m_ulSamplesPerSecond + ulRealTime;

    if (m_bIsGapInStreaming)
    {
	audioData.uAudioStreamType	= TIMED_AUDIO;
	m_bIsGapInStreaming		= FALSE;
    }
    else
    {
	audioData.uAudioStreamType	= STREAMING_AUDIO;
    }

    if (AdjustAudioData(audioData))
    {
	m_pAudioStream->Write( &audioData );
    }

    m_ulCurrentTime  = audioData.ulAudioTime;

    pBuffer->Release();
    //Release audioData.pData, not pNewBuffer, because audioData.pData may
    // have been released and changed to a new buffer in AdjustAudioData,
    // above; This ensures that the correct object gets cleaned up:
    HX_RELEASE(audioData.pData);

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnTimeSync
//  Purpose:
//	Called by client engine to inform the renderer of the current
//	time relative to the streams synchronized time-line. The 
//	renderer should use this time value to update its display or
//	render it's stream data accordingly.  As far as .WAV files
//	are concerned, Audio Services takes care of the rendering, as
//	long as we are supplying it with packets of data.
//
STDMETHODIMP CPCMRenderer::OnTimeSync(ULONG32 ulTime)
{
#ifdef DEBUG_LOG
	fprintf(m_pLogFile, "CPCMRenderer::OnTimeSync() @ time: %lu.%03lu\n",
		ulTime/1000, ulTime%1000);
	fflush(m_pLogFile);
#endif

    m_ulLastTime = ulTime;
    
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPreSeek
//  Purpose:
//	Called by client engine to inform the renderer that a seek is
//	about to occur. The render is informed the last time for the 
//	stream's time line before the seek, as well as the first new
//	time for the stream's time line after the seek will be completed.
//
STDMETHODIMP CPCMRenderer::OnPreSeek(ULONG32 ulOldTime, ULONG32 ulNewTime)
{
#ifdef DEBUG_LOG
    fprintf(m_pLogFile, "CPCMRenderer::OnPreSeek("
	    "from time: %lu.%03lu to time: %lu.%03lu)\n",
	    ulOldTime/1000, ulOldTime%1000,
	    ulNewTime/1000, ulNewTime%1000);
    fflush(m_pLogFile);
#endif

    m_bIsGapInStreaming = TRUE;
    m_bInSeekMode	= TRUE;
    m_bPacketsDone	= FALSE;

    m_bFirstPacket	= TRUE;

    if (m_bStreamDry && m_pStream)
    {
	m_pStream->ReportRebufferStatus(1, 1);
	m_bStreamDry = FALSE;
	m_chBufferingCount = 0;
    }

    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPostSeek
//  Purpose:
//	Called by client engine to inform the renderer that a seek has
//	just occured. The render is informed the last time for the 
//	stream's time line before the seek, as well as the first new
//	time for the stream's time line after the seek.
//
STDMETHODIMP CPCMRenderer::OnPostSeek(ULONG32 ulOldTime, ULONG32 ulNewTime)
{
#ifdef DEBUG_LOG
    fprintf(m_pLogFile, "CPCMRenderer::OnPostSeek("
	    "from time: %lu.%03lu to time: %lu.%03lu)\n",
	    ulOldTime/1000, ulOldTime%1000,
	    ulNewTime/1000, ulNewTime%1000);
    fflush(m_pLogFile);
#endif

    m_bInSeekMode   = FALSE;
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnPause
//  Purpose:
//	Called by client engine to inform the renderer that a pause has
//	just occured. The render is informed the last time for the 
//	stream's time line before the pause.
//
STDMETHODIMP CPCMRenderer::OnPause(ULONG32 ulTime)
{
#ifdef DEBUG_LOG
    fprintf(m_pLogFile, "CPCMRenderer::OnPause()\n");
    fflush(m_pLogFile);
#endif
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnBegin
//  Purpose:
//	Called by client engine to inform the renderer that a begin or
//	resume has just occured. The render is informed the first time 
//	for the stream's time line after the resume.
//
STDMETHODIMP CPCMRenderer::OnBegin(ULONG32 ulTime)
{
#ifdef DEBUG_LOG
    fprintf(m_pLogFile, "CPCMRenderer::OnBegin()\n");
    fflush(m_pLogFile);
#endif
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::OnBuffering
//  Purpose:
//	Called by client engine to inform the renderer that buffering
//	of data is occuring. The render is informed of the reason for
//	the buffering (start-up of stream, seek has occured, network
//	congestion, etc.), as well as percentage complete of the 
//	buffering process.
//
STDMETHODIMP CPCMRenderer::OnBuffering(ULONG32 ulFlags, UINT16 unPercentComplete)
{
#ifdef DEBUG_LOG
    fprintf(m_pLogFile, "CPCMRenderer::OnBuffering("
	    "ulFlags=%x, percentComplete=%u)\n",
	    ulFlags, unPercentComplete);
    fflush(m_pLogFile);
#endif
    return HXR_OK;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IHXRenderer::GetDisplayType
//  Purpose:
//	Called by client engine to ask the renderer for it's preferred
//	display type. When layout information is not present, the 
//	renderer will be asked for it's prefered display type. Depending
//	on the display type a buffer of additional information may be 
//	needed. This buffer could contain information about preferred
//	window size.
//
STDMETHODIMP CPCMRenderer::GetDisplayType
(
    REF(HX_DISPLAY_TYPE)   ulFlags,
    REF(IHXBuffer*)	    pBuffer
)
{
    //  Our renderer needs no visible display.
    ulFlags = HX_DISPLAY_NONE;

    return HXR_OK;
}

/************************************************************************
 *	Method:
 *	    IHXRenderer::OnEndofPackets
 *	Purpose:
 *	    Called by client engine to inform the renderer that all the
 *	    packets have been delivered. However, if the user seeks before
 *	    EndStream() is called, renderer may start getting packets again
 *	    and the client engine will eventually call this function again.
 */
STDMETHODIMP CPCMRenderer::OnEndofPackets(void)
{
#ifdef DEBUG_LOG
    fprintf(m_pLogFile, "CPCMRenderer::OnEndofPackets()\n");
    fflush(m_pLogFile);
#endif

    m_bPacketsDone = TRUE;

    if (m_bStreamDry && m_pStream)
    {
	m_pStream->ReportRebufferStatus(1, 1);
	m_bStreamDry = FALSE;
	m_chBufferingCount = 0;
    }

    return HXR_OK;
}


/************************************************************************
 *  Method:
 *      IHXDryNotification::OnDryNotification
 *  Purpose:
 *	    This function is called when it is time to write to audio device 
 *	    and there is not enough data in the audio stream. The renderer can
 *	    then decide to add more data to the audio stream. This should be 
 *	    done synchronously within the call to this function.
 *	    It is OK to not write any data. Silence will be played instead.
 */
STDMETHODIMP CPCMRenderer::OnDryNotification(UINT32 /*IN*/ ulCurrentStreamTime,
					     UINT32 /*IN*/ ulMinimumDurationRequired)
{
    if ((!m_bPacketsDone) && m_pStream)
    {
// {FILE* f1 = ::fopen("c:\\aurend.txt", "a+"); ::fprintf(f1, "DryNotification tickCount: %11lu\n", HX_GET_BETTERTICKCOUNT());::fclose(f1);}

	m_pStream->ReportRebufferStatus(DRY_BUFFER_COUNT, 0); 
	m_bStreamDry = TRUE;
	m_chBufferingCount = 0;
    }
    
    return HXR_OK;
}


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

    m_lTimeOffset = lTimeOffset;

    if (m_pAudioStream &&
	HXR_OK == m_pAudioStream->QueryInterface(IID_IHXUpdateProperties, (void**)&pUpdateProperties))
    {
	pUpdateProperties->UpdatePacketTimeOffset(lTimeOffset);
    }
    HX_RELEASE(pUpdateProperties);	

    return HXR_OK;
}

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

UINT32  
CPCMRenderer::ConvertMsToBytes(UINT32 ulMs)
{
    UINT32		ulResult = 0;
    UINT32		ulBytesPerSecond = 0;
  
    ulBytesPerSecond = m_audioFmt.uChannels * m_audioFmt.uBitsPerSample / 8 *
		       m_audioFmt.ulSamplesPerSec;

    ulResult = (UINT32)((ulMs * 1.0 / 1000) * ulBytesPerSecond);

    return ulResult;
}

UINT32  
CPCMRenderer::ConvertBytesToMs(UINT32 ulBytes)
{    	
    UINT32		ulResult = 0;
 
    ulResult = (UINT32)((1000.0 
		/ (m_audioFmt.uChannels * ((m_audioFmt.uBitsPerSample==8)?1:2) 
		*  m_audioFmt.ulSamplesPerSec)) 
		*  ulBytes);

    return ulResult;
}

BOOL    
CPCMRenderer::AdjustAudioData(REF(HXAudioData) audioData)
{
    BOOL	bResult = TRUE;
    UINT32	ulDuration = 0;
    UINT32	ulSize = 0;
    UINT32	ulExtraInMs = 0;
    UINT32	ulExtraInBytes = 0;
    IHXBuffer* pBuffer = NULL;

    ulSize = (audioData.pData)->GetSize();

    // caculate the worth of this data in ms
    ulDuration = ConvertBytesToMs(ulSize);

    // trim off any extra bytes
    if (audioData.ulAudioTime < m_ulTrackStartTime)
    {
	if ((audioData.ulAudioTime + ulDuration) <= m_ulTrackStartTime)
	{	 	    
	    bResult = FALSE;
	    goto cleanup;
	}
	else
	{
	    // trim off partial data
	    ulExtraInMs = m_ulTrackStartTime - audioData.ulAudioTime;

	    // convert to bytes
	    ulExtraInBytes = ConvertMsToBytes(ulExtraInMs);

	    // align in sample boundary
	    ulExtraInBytes -= (ulExtraInBytes % (m_audioFmt.uBitsPerSample * 
			       m_audioFmt.uChannels / 8));

	    if (!m_pCommonClassFactory  ||
		    HXR_OK != m_pCommonClassFactory->CreateInstance(
		    CLSID_IHXBuffer, (void**)&pBuffer))
	    {
		bResult = FALSE;
		goto cleanup;
	    }

	    pBuffer->Set((audioData.pData)->GetBuffer() + ulExtraInBytes, 
			 ulSize - ulExtraInBytes);

	    //Release and replace with new buffer:
	    HX_RELEASE(audioData.pData);

	    audioData.pData = pBuffer;	 	 
	    audioData.ulAudioTime = m_ulTrackStartTime;
	}
    }
    
    if ((audioData.ulAudioTime + ulDuration) > m_ulTrackEndTime)
    {
	if (audioData.ulAudioTime >= m_ulTrackEndTime)
	{
	    // drop this one
	    bResult = FALSE;
	    goto cleanup;
	}
	else
	{
	    // trim off the extra ones
	    ulExtraInMs = (audioData.ulAudioTime + ulDuration) - m_ulTrackEndTime;

	    // convert to bytes
	    ulExtraInBytes = ConvertMsToBytes(ulExtraInMs);

	    // align in sample boundary
	    ulExtraInBytes -= (ulExtraInBytes % (m_audioFmt.uBitsPerSample * 
			       m_audioFmt.uChannels / 8));

	    if (!m_pCommonClassFactory  ||
		    HXR_OK != m_pCommonClassFactory->CreateInstance(
		    CLSID_IHXBuffer, (void**)&pBuffer))
	    {
		bResult = FALSE;
		goto cleanup;
	    }

	    pBuffer->Set((audioData.pData)->GetBuffer(), ulSize - ulExtraInBytes);

	    //Release and replace with new buffer:
	    HX_RELEASE(audioData.pData);

	    audioData.pData = pBuffer;	    
	}
    }

    if (m_lTimeOffset < 0)
    {
	audioData.ulAudioTime  += (UINT32) (-m_lTimeOffset);
    }
    else
    {
	HX_ASSERT(audioData.ulAudioTime >= (UINT32)m_lTimeOffset);
	audioData.ulAudioTime -= (UINT32)m_lTimeOffset;
    }

cleanup:
    return bResult;
}
