/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE OGG VORBIS PROJECT SOURCE CODE.         *
 *                                                                  *
 * THE OGG VORBIS PROJECT SOURCE CODE IS (C) COPYRIGHT 1994-2001    *
 * by the XIPHOPHORUS Company http://www.xiph.org/                  *

 ********************************************************************

 function: implementation of the render plugin for RealSystem

********************************************************************/
/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: rvorbis.cpp,v 1.8.2.6 2004/11/24 18:29:53 acolwell 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 and/or licensor 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 ***** */


#define INITGUID

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

#include <ogg/ogg.h>
#include <vorbis/codec.h>

#include "hxtypes.h"
#include "hxcom.h"
#include "hxcore.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxplugn.h"
#include "hxrendr.h"
#include "hxausvc.h"
#include "hxmon.h"
#include "hxtick.h" // IsTimeLess(), IsTimeGreaterOrEqual()
#include "pckunpck.h"
#include "rvorbis.h"

#include "ogg_depack.h" // OggDepacketizer

#include "hxassert.h"
#include "debug.h" // DPRINTF()
#define D_VORBIS 0 //0x2000000

#define NO_TIME_SET 0xffffffff

const UINT32 InitialPushdownDuration = 500;

STDAPI RMACreateInstance(IUnknown **ppRendererObj)
{
#ifdef _DEBUG
    debug_level() |= D_VORBIS;
#endif /* _DEBUG */

    *ppRendererObj = (IUnknown *)(IHXPlugin *)new CVorbisRenderer();
    if (*ppRendererObj != NULL) {
        (*ppRendererObj)->AddRef();
        return HXR_OK;
    }

    return HXR_OUTOFMEMORY;
}

const char *CVorbisRenderer::zm_pDescription = DESCRIPTION;
const char *CVorbisRenderer::zm_pCopyright = COPYRIGHT;
const char *CVorbisRenderer::zm_pMoreInfoURL = MORE_INFO_URL;

const VorbisDepackInfo CVorbisRenderer::zm_depackInfo[] = {
    {"application/x-ogg", &OggDepacketizer::Create},
    {"application/ogg", &OggDepacketizer::Create}
};

const VorbisTagMapping CVorbisRenderer::zm_tagInfo[] = 
{
    {"TITLE", "Title"},
    {"ARTIST", "Author"},
    {"AUTHOR", "Author"},
    {"DESCRIPTION", "Description"},
    {"COPYRIGHT", "Copyright"}
};

CVorbisRenderer::CVorbisRenderer(void)
    : m_RefCount(0),
      m_pClassFactory(NULL),
      m_bInSeekMode(FALSE),
      m_bIsGapInStreaming(TRUE),
      m_bEndOfPackets(FALSE),
      m_bRebuffering(FALSE),
      m_bIsFirstPacket(TRUE),
      m_lTimeOffset(0),
      m_VorbisState(VorbisInitialHeader),
      m_totalSamples(0),
      m_uAudioPushdownStartTS(0),
      m_pContext(NULL),
      m_pPlayer(NULL),
      m_pStream(NULL),
      m_pAudioPlayer(NULL),
      m_pStreamHeaderObj(NULL),
      m_pCurrentStream(NULL),
      m_pDepack(NULL),
      m_pStreamMimeTypes(NULL)
{
    vorbis_info_init(&m_vi);
    vorbis_comment_init(&m_vc);

    int mimetypeCt =  DepackInfoCount();
    m_pStreamMimeTypes = new const char*[mimetypeCt + 1];

    if (m_pStreamMimeTypes)
    {
        for (int i = 0; i < mimetypeCt; i++)
        {
            m_pStreamMimeTypes[i] = zm_depackInfo[i].m_pMimeType;
        }
        m_pStreamMimeTypes[mimetypeCt] = NULL;
    }
}

STDMETHODIMP CVorbisRenderer::GetPluginInfo(
    REF(BOOL) bLoadMultiple,
    REF(const char *) pDescription,
    REF(const char *) pCopyright,
    REF(const char *) pMoreInfoURL,
    REF(UINT32) versionNumber
    )
{
    bLoadMultiple = TRUE;
    pDescription = zm_pDescription;
    pCopyright = zm_pCopyright;
    pMoreInfoURL = zm_pMoreInfoURL;
    versionNumber = PLUGIN_VERSION_NUM;

    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::GetRendererInfo(
    REF(const char **) pStreamMimeTypes,
    REF(UINT32) initialGranularity
    )
{
    HX_RESULT res = HXR_FAILED;

    if (m_pStreamMimeTypes)
    {
        pStreamMimeTypes = m_pStreamMimeTypes;
        initialGranularity = TIME_SYNC_FREQ;

        res = HXR_OK;
    }

    return res;
}

STDMETHODIMP CVorbisRenderer::InitPlugin(IUnknown *pRMACore)
{
    m_pContext = pRMACore;
    HX_ADDREF(m_pContext);

    pRMACore->QueryInterface(IID_IHXCommonClassFactory, 
                             (void **)&m_pClassFactory);
    if (m_pClassFactory == NULL)
        return HXR_NOTIMPL;

    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::StartStream(IHXStream *pStream, 
                                          IHXPlayer *pPlayer)
{
    DPRINTF(D_VORBIS,("CVorbisRenderer::StartStream()\n"));

    m_pStream = pStream;
    m_pPlayer = pPlayer;

    HX_ADDREF(m_pStream);
    HX_ADDREF(m_pPlayer);

    // get interface to audio player
    m_pPlayer->QueryInterface(IID_IHXAudioPlayer, (void **)&m_pAudioPlayer);

    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::OnHeader(IHXValues* pStreamHdr)
{
    HX_RESULT res = HXR_FAILED;

    if (pStreamHdr)
    {
        IHXBuffer* pMimeBuf = NULL;

        if (HXR_OK == pStreamHdr->GetPropertyCString("MimeType", pMimeBuf))
        {
            const char* pMimeType = (const char*)pMimeBuf->GetBuffer();
            
            //printf ("CVorbisRenderer::OnHeader() mimetype = '%s'\n", 
            //        pMimeType);
            
            /* search for a depacketizer that handles this mimetype */
            for (int i = 0; !m_pDepack && (i < DepackInfoCount()); i++)
            {
                if (!strcasecmp(pMimeType, zm_depackInfo[i].m_pMimeType))
                {
                    /* We found a match. Build the depacketizer */
                    m_pDepack = zm_depackInfo[i].m_fpCreateFunc();
                }
            }

        }
        HX_RELEASE(pMimeBuf);
        
        ULONG32 ulDuration = NO_TIME_SET;
        pStreamHdr->GetPropertyULONG32("Duration", ulDuration);

        if (HXR_OK != pStreamHdr->GetPropertyULONG32("TrackStartTime", 
                                                     m_ulTrackStartTime))
        {
            m_ulTrackStartTime = 0;
        }
        
        if (HXR_OK != pStreamHdr->GetPropertyULONG32("TrackEndTime", 
                                                     m_ulTrackEndTime))
        {
            m_ulTrackEndTime = ulDuration;
        }
    }
    
    if (!m_pDepack)
    {
        res = HXR_OUTOFMEMORY;
    }
    else if ((HXR_OK == (res = m_pDepack->Init(m_pContext))) &&
             (HXR_OK == (res = m_pDepack->OnStreamHeader(pStreamHdr))))
    {
        
        /* cache stream header for use later */
        HX_RELEASE(m_pStreamHeaderObj);
        m_pStreamHeaderObj = pStreamHdr;
        HX_ADDREF(m_pStreamHeaderObj);
        
        res = InitAudioStream(DEFAULT_VORBIS_SAMPLE_RATE, 
                              DEFAULT_VORBIS_CHANNELS);
    }

    return res;
}

STDMETHODIMP CVorbisRenderer::OnBegin(UINT32 ulTime)
{
    DPRINTF(D_VORBIS,("OnBegin(%u)\n", ulTime));

    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::GetDisplayType(
    REF(HX_DISPLAY_TYPE) displayType,
    REF(IHXBuffer *) pDisplayInfo
    )
{
    displayType = HX_DISPLAY_NONE;
    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::OnPacket(IHXPacket *pPacketObj, 
                                       INT32 lTimeOffset)
{
    HX_RESULT res = HXR_UNEXPECTED;

    DPRINTF(D_VORBIS,("Entering OnPacket() timestamp %u, offset %d\n", 
                      pPacketObj->GetTime(), lTimeOffset));

    if (m_bInSeekMode)
    {
        // ignore pre-seek packets
        res = HXR_OK;
    }
    else if (m_pDepack)
    {
	if (m_bIsFirstPacket)
	{
            m_lTimeOffset = lTimeOffset;

	    // Calculate current position based off the packet timestamp
	    m_totalSamples = (((ogg_int64_t)pPacketObj->GetTime()) * 
                              m_audioFmt.ulSamplesPerSec) / 1000;
            

            if (VorbisPlay == m_VorbisState)
            {
                m_VorbisState = VorbisWriteInitialPushdown;
                m_uAudioPushdownStartTS = CurrentTime();
            }

	    m_bIsGapInStreaming = TRUE;
	    m_bIsFirstPacket = FALSE;
	}

        res = m_pDepack->OnPacket(pPacketObj);

        if (!pPacketObj->IsLost()) 
        {
            HX_RESULT err = HXR_OK;
            while ((HXR_OK == err) && (VorbisPlay != m_VorbisState))
            {
                ogg_packet* pOp = NULL;
            
                err = m_pDepack->GetVorbisPacket(pOp);

                if (HXR_OK == err)
                {
                    switch (m_VorbisState) {
                    case VorbisWriteInitialPushdown:
                        if ((pOp->packet[0] & 0x1) == 0)
                        {
                            DPRINTF(D_VORBIS,("Got initial pushdown packet\n"));

                            DecodeAndRender(pOp);

                            if (IsTimeGreaterOrEqual(CurrentTime(),
                                                     m_uAudioPushdownStartTS + 
                                                     InitialPushdownDuration))
                            {
                                m_VorbisState = VorbisPlay;
                            }
                        }
                        else if (pOp->packet[0] ==  1)
                        {
                            gotoInitialHeaderState();
                            res = handleInitialHeader(pOp);
                        }
                        else
                        {
                            // This isn't an ident header like
                            // we expect.
                            err = HXR_UNEXPECTED;
                        }
                        break;
                    case VorbisInitialHeader:
                        res = handleInitialHeader(pOp);
                        break;
        
                    case VorbisCommentHeader:
                        if (vorbis_synthesis_headerin(&m_vi, &m_vc, pOp) >= 0)
                        {
                            DPRINTF(D_VORBIS,("Got Comment Header\n"));
                            updateTACInfo(&m_vc);
                            m_VorbisState = VorbisCodebookHeader;
                        }
                        break;
        
                    case VorbisCodebookHeader:
                        vorbis_synthesis_headerin(&m_vi, &m_vc, pOp);
                        vorbis_synthesis_init(&m_vd, &m_vi);
                        vorbis_block_init(&m_vd, &m_vb);
                        
                        m_VorbisState = VorbisWriteInitialPushdown;
                        m_uAudioPushdownStartTS = CurrentTime();

                        DPRINTF(D_VORBIS,("Got Codebooks\n"));
                        break;
                    };

                    _ogg_free(pOp);
                }
            }

            if (IsRebuffering())
            {
                EndRebuffer();
            }
        }
    }

    DPRINTF(D_VORBIS,("Leaving OnPacket() %08x\n", res));

    return res;
}

UINT32 CVorbisRenderer::CurrentTime() const
{
    ogg_int64_t den = m_audioFmt.ulSamplesPerSec;
    ogg_int64_t q = m_totalSamples / den;
    ogg_int64_t r = m_totalSamples - q * den;

    return (UINT32)(q * 1000 + (r * 1000) / den);
}

UINT32 CVorbisRenderer::BytesToMs(UINT32 ulNumBytes)
{
    UINT32 q = ulNumBytes / m_ulBytesPerSec;
    UINT32 r = ulNumBytes - q * m_ulBytesPerSec;

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

UINT32 CVorbisRenderer::MsToBytes(UINT32 ulMs)
{
    UINT32 q = ulMs / 1000;
    UINT32 r = ulMs - q * 1000;

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

STDMETHODIMP CVorbisRenderer::OnDryNotification(UINT32 ulCurrentStreamTime, 
                                                UINT32 ulMinimumDurationRequired)
{
    HX_RESULT res = HXR_UNEXPECTED;

    DPRINTF(D_VORBIS,("OnDryNotification(%u %u)\n", 
                      ulCurrentStreamTime, ulMinimumDurationRequired));

    if (VorbisPlay != m_VorbisState)
    {
        res = HXR_OK;   
    }
    else if (m_pDepack)
    {
        BOOL bDone = FALSE;

        UINT32 ulEndTime = ulCurrentStreamTime + ulMinimumDurationRequired;
        BOOL bWroteData = FALSE;
        UINT32 ulLastWriteTime = 0;
        BOOL bAudioParamsChanged = FALSE;
        BOOL bEOS = FALSE;
        
        while (!bDone)
        {
            ogg_packet* pPkt = NULL;
            HX_RESULT err = m_pDepack->GetVorbisPacket(pPkt);
            
            if (HXR_OK == err)
            {

                if (pPkt->b_o_s)
                {
                    gotoInitialHeaderState();
                }
                else if (pPkt->e_o_s)
                {
                    bEOS = TRUE;
                }
                else
                {
                    HX_ASSERT(!bEOS);
                }

                switch (m_VorbisState) {
                case VorbisInitialHeader:
                    res = handleInitialHeader(pPkt);
                    bEOS = FALSE;
                    break;
                    
                case VorbisCommentHeader:
                    if (vorbis_synthesis_headerin(&m_vi, &m_vc, pPkt) >= 0)
                    {
                        DPRINTF(D_VORBIS,("Got Comment Header\n"));
                        updateTACInfo(&m_vc);
                        m_VorbisState = VorbisCodebookHeader;
                    }
                    break;
                    
                case VorbisCodebookHeader:
                    vorbis_synthesis_headerin(&m_vi, &m_vc, pPkt);
                    vorbis_synthesis_init(&m_vd, &m_vi);
                    vorbis_block_init(&m_vd, &m_vb);
                    
                    m_VorbisState = VorbisPlay;
        
                    DPRINTF(D_VORBIS,("Got Codebooks\n"));
                    break;
                case VorbisPlay:
                    DPRINTF(D_VORBIS,("calling DecodeAndRender\n"));

                    ulLastWriteTime = DecodeAndRender(pPkt);

                    bWroteData = TRUE;

                    bDone =  IsTimeGreaterOrEqual(ulLastWriteTime,ulEndTime);

                    break;

                default:
                    HX_ASSERT(!"Unexpected state!");
                    bDone = TRUE;
                    break;
                };
                _ogg_free(pPkt);
            }
            else 
            { 
                bDone = TRUE;
            }
        }

        if (!bWroteData || 
            IsTimeLess(ulLastWriteTime, ulEndTime) &&
            !IsRebuffering() && !bEOS)
        {
            /* We ran out of data and we didn't satisfy what
             * is needed. Initiate a rebuffer so we get more
             * packets
             */
            StartRebuffer();
        }
        else if (bWroteData && 
                 IsTimeGreaterOrEqual(ulLastWriteTime,ulEndTime) &&
                 IsRebuffering())
        {
            /* We wrote data, satisfied what was needed and
             * were rebuffering. Time to end the rebuffer.
             */
            EndRebuffer();
        }
        
        if (bEOS)
        {
            /* We don't have any more data and we've seen an
             * end of stream packet. This could be the start
             * of a video only stream group. Disable ODN
             * calls for now so we don't rebuffer and
             * transition to the VorbisInitialHeader state so 
             * that we are ready for the next stream  and so
             * OnPacket will process the packets. 
             */
            m_pCurrentStream->SetDryNotification(NULL);
            gotoInitialHeaderState();
        }

        DPRINTF(D_VORBIS,("needed %ldms sent %ldms...\n", 
                          ulMinimumDurationRequired, 
                          (bWroteData) ? (ulLastWriteTime - ulCurrentStreamTime) : 0));

        res = HXR_OK;
    }

    DPRINTF(D_VORBIS,("OnDryNotification() : res %08x\n", res));

    return res;
}

STDMETHODIMP CVorbisRenderer::OnTimeSync(UINT32 currentPlayBackTime)
{
    DPRINTF(D_VORBIS,("OnTimeSync(%u)\n", currentPlayBackTime));

    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::OnPreSeek(UINT32 timeBeforeSeek, UINT32 timeAfterSeek)
{
    DPRINTF(D_VORBIS,("OnPreSeek(%u, %u)\n", timeBeforeSeek, timeAfterSeek));

    m_bInSeekMode = TRUE;
    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::OnPostSeek(UINT32 timeBeforeSeek, UINT32 timeAfterSeek)
{
    DPRINTF(D_VORBIS,("OnPostSeek(%u, %u)\n", timeBeforeSeek, timeAfterSeek));

    m_bInSeekMode = FALSE;
    
    m_bIsFirstPacket = TRUE;

    if (m_pDepack)
    {
        m_pDepack->Reset();
    }
        
    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::OnPause(UINT32 timeBeforePause)
{
    DPRINTF(D_VORBIS,("CVorbisRenderer::OnPause(%u)\n", timeBeforePause));

    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::OnBuffering(UINT32 reason, UINT16 percentComplete)
{
    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::OnEndofPackets(void)
{
    DPRINTF(D_VORBIS,("CVorbisRenderer::OnEndofPackets()\n"));

    if (m_pDepack)
    {
        m_pDepack->EndOfStream();
    }

    m_bEndOfPackets = TRUE;

    if (IsRebuffering())
    {
        EndRebuffer();
    }

    return HXR_OK;
}

STDMETHODIMP CVorbisRenderer::EndStream(void)
{
    DPRINTF(D_VORBIS,("CVorbisRenderer::EndStream()\n"));

    Shutdown();
        
    return HXR_OK;
}

CVorbisRenderer::~CVorbisRenderer(void)
{    
    Shutdown();
}

STDMETHODIMP_(UINT32) CVorbisRenderer::AddRef(void)
{
    return InterlockedIncrement(&m_RefCount);
}

STDMETHODIMP_(UINT32) CVorbisRenderer::Release(void)
{
    if (InterlockedDecrement(&m_RefCount) > 0)
        return m_RefCount;

    delete this;
    return 0;
}

STDMETHODIMP CVorbisRenderer::QueryInterface(REFIID interfaceID, void **ppInterfaceObj)
{
    if (IsEqualIID(interfaceID, IID_IUnknown)) {
        AddRef();
        *ppInterfaceObj = (IUnknown *)(IHXPlugin *)this;
        return HXR_OK;
    } else if (IsEqualIID(interfaceID, IID_IHXPlugin)) {
        AddRef();
        *ppInterfaceObj = (IHXPlugin *)this;
        return HXR_OK;
    } else if (IsEqualIID(interfaceID, IID_IHXRenderer)) {
        AddRef();
        *ppInterfaceObj = (IHXRenderer *)this;
        return HXR_OK;
    }

    *ppInterfaceObj = NULL;
    return HXR_NOINTERFACE;
}

HX_RESULT 
CVorbisRenderer::InitAudioStream(UINT32 ulSampleRate, UINT16 usChannels)
{
    HX_RESULT res = HXR_OK;

    DPRINTF(D_VORBIS,("InitAudioStream(%u, %u)\n", ulSampleRate, usChannels));

    UINT32 uCurrentSampleRate = 0;
    UINT32 uCurrentChannels = 0;

    if (m_pCurrentStream)
    {
        res = m_pCurrentStream->GetStreamInfo(uCurrentSampleRate,
                                              uCurrentChannels);

        m_pCurrentStream->SetDryNotification(NULL);
        m_pCurrentStream = NULL;
    }

    if ((uCurrentSampleRate != ulSampleRate) &&
        m_totalSamples && uCurrentSampleRate)
    {
        ogg_int64_t q = m_totalSamples / uCurrentSampleRate;
        ogg_int64_t r = m_totalSamples - q * uCurrentSampleRate;
        m_totalSamples = 
            q * ulSampleRate + (r * ulSampleRate) / uCurrentSampleRate;
    }

    m_audioFmt.ulSamplesPerSec = ulSampleRate;
    m_audioFmt.uChannels = usChannels;
    m_audioFmt.uBitsPerSample = 16;
    m_audioFmt.ulSamplesPerSec = ulSampleRate;
    m_audioFmt.uMaxBlockSize = 4096;
    
    m_ulBytesPerSec = (m_audioFmt.uChannels * 
                       (m_audioFmt.uBitsPerSample / 8) * 
                       m_audioFmt.ulSamplesPerSec);
    
    m_pCurrentStream = findAudioStream(m_audioFmt.ulSamplesPerSec,
                                       m_audioFmt.uChannels);

    if (m_pCurrentStream)
    {
        m_pCurrentStream->SetDryNotification(this);
    }
    else
    {
        res = HXR_UNEXPECTED;
    }

    return res;
}

COggAudioStreamHelper* 
CVorbisRenderer::findAudioStream(UINT32 ulSampleRate, 
                                 UINT16 usChannels)
{
    COggAudioStreamHelper* pRet = NULL;

    CHXSimpleList::Iterator itr = m_audioStreams.Begin();

    for (;!pRet && (itr != m_audioStreams.End()); ++itr)
    {
        COggAudioStreamHelper* pHlpr = 
            (COggAudioStreamHelper*)*itr;
        
        UINT32 uSampRate;
        UINT32 uChannels;

        if ((HXR_OK == pHlpr->GetStreamInfo(uSampRate, uChannels)) &&
            (uSampRate == ulSampleRate) &&
            (usChannels == uChannels))
        {
            pRet = pHlpr;
            pRet->AddRef();
        }
    }

    if (!pRet)
    {
        IHXAudioStream* pAudioStream = NULL;
        HX_RESULT res = m_pAudioPlayer->CreateAudioStream(&pAudioStream);

        if (HXR_OK == res)
        {
            pRet = new COggAudioStreamHelper;

            if (pRet)
            {
                pRet->AddRef(); // For return value
                res = pRet->Init(pAudioStream);

                if (HXR_OK == res)
                {
                    if (m_audioStreams.AddTail(pRet))
                    {
                        pRet->AddRef(); // For list
                        res = pAudioStream->Init(&m_audioFmt, 
                                                 m_pStreamHeaderObj);

                        if (HXR_OK != res)
                        {
                            pRet->Release(); // For list
                            m_audioStreams.RemoveTail();
                        }
                    }
                    else
                    {
                        res = HXR_OUTOFMEMORY;
                    }
                }
                            
                if (HXR_OK != res)
                {
                    HX_RELEASE(pRet); // For return value
                }
            }
        }

        HX_RELEASE(pAudioStream);
    }

    return pRet;
}

UINT32 CVorbisRenderer::DecodeAndRender(ogg_packet* pOp)
{
    UINT32 ulLastWriteTime = 0;

    // we have a packet to decode
    float **pcm;
    int samples;
    int convsize = m_audioFmt.uMaxBlockSize / m_vi.channels;

    if (vorbis_synthesis(&m_vb, pOp) == 0)
        vorbis_synthesis_blockin(&m_vd, &m_vb);
    else {
        DPRINTF(D_VORBIS,("ERROR ? HMMMMMMM\n"));
    }

    UINT32 ulAudioTime = CurrentTime();

    while ((samples = vorbis_synthesis_pcmout(&m_vd, &pcm)) > 0) {
        int i, j;
        int clipflag = 0;
        int bout = (samples < convsize ? samples : convsize);
        int audioByteCt = bout * m_vi.channels * 2;

        HXAudioData audioData;
        audioData.pData = NULL;
        audioData.ulAudioTime = ulAudioTime;
        
        if ((HXR_OK == m_pClassFactory->CreateInstance(CLSID_IHXBuffer, 
                                               (void **)&audioData.pData)) &&
            (HXR_OK == audioData.pData->SetSize(audioByteCt)))
        {
                                                
            // convert floats to 16bit signed ints (host order) and interleave
            for (i = 0; i < m_vi.channels; i++) {
                ogg_int16_t *ptr = ((ogg_int16_t*)audioData.pData->GetBuffer()) + i;
                float *mono = pcm[i];
                                                        
                for (j = 0; j < bout; j++) {
                    int val = (int)(mono[j] * 32767.f);
                    // guard against clipping
                    if (val > 32767) {
                        val = 32767;
                        clipflag = 1;
                    }
                    if (val < -32768) {
                        val = -32768;
                        clipflag = 1;
                    }
                    *ptr = val;
                    ptr += m_vi.channels;
                }
            }

            BOOL bWrite = AdjustAudioData(audioData);

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

            if (bWrite)
            {
                ulLastWriteTime = audioData.ulAudioTime;

                HX_RESULT err = m_pCurrentStream->Write(&audioData);
                DPRINTF(D_VORBIS,
                        ("write err 0x%08x samples %d audioTime %u\n", 
                         err, bout, audioData.ulAudioTime));
            }
        }
        else
        {
            /* We failed to create a buffer so make it
             * look like there was a gap in the audio
             */
            m_bIsGapInStreaming = TRUE;
        }

        HX_RELEASE(audioData.pData);

        m_totalSamples += bout;
            

        // tell vorbis how many samples we consumed
        vorbis_synthesis_read(&m_vd, bout);
    }

    return ulLastWriteTime;
}

void CVorbisRenderer::Shutdown()
{
    vorbis_comment_clear(&m_vc);
    vorbis_info_clear(&m_vi);

    HX_RELEASE(m_pContext);
    HX_RELEASE(m_pClassFactory);
    HX_RELEASE(m_pStream);
    HX_RELEASE(m_pPlayer);
    HX_RELEASE(m_pAudioPlayer);
    HX_RELEASE(m_pStreamHeaderObj);

    HX_RELEASE(m_pCurrentStream);

    while(!m_audioStreams.IsEmpty())
    {
        COggAudioStreamHelper* pHlpr = 
            (COggAudioStreamHelper*)m_audioStreams.RemoveHead();

        pHlpr->Close();
        HX_RELEASE(pHlpr);
    }

    HX_DELETE(m_pDepack);
}

void CVorbisRenderer::StartRebuffer()
{
    /* Only start a rebuffer if we have a stream
     * and we haven't had OnEndofPackets() called
     */

    if (m_pStream && !m_bEndOfPackets)
    {
        DPRINTF(D_VORBIS,("Starting rebuffer\n"));

        m_bRebuffering = TRUE;
        m_pStream->ReportRebufferStatus(1, 0);
    }
}

void CVorbisRenderer::EndRebuffer()
{
    m_bRebuffering = FALSE;

    if (m_pStream)
    {
        DPRINTF(D_VORBIS,("Ending rebuffer\n"));
        m_pStream->ReportRebufferStatus(1, 1);
    }
}

int CVorbisRenderer::DepackInfoCount()
{
    return sizeof(zm_depackInfo) / sizeof(VorbisDepackInfo);
}

BOOL CVorbisRenderer::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();

    // calculate the worth of this data in ms
    ulDuration = BytesToMs(ulSize);

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

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

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

	    if (!m_pClassFactory  ||
                HXR_OK != m_pClassFactory->CreateInstance(
		    CLSID_IHXBuffer, (void**)&pBuffer))
	    {
		bResult = FALSE;
	    }
            else
            {
                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 (bResult && (m_ulTrackEndTime != NO_TIME_SET) &&
        (audioData.ulAudioTime + ulDuration) > m_ulTrackEndTime)
    {
	if (audioData.ulAudioTime >= m_ulTrackEndTime)
	{
	    // drop this one
	    bResult = FALSE;
	}
	else
	{
	    // trim off the extra ones
	    ulExtraInMs = (audioData.ulAudioTime + ulDuration) - m_ulTrackEndTime;

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

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

	    if (!m_pClassFactory  ||
		    HXR_OK != m_pClassFactory->CreateInstance(
		    CLSID_IHXBuffer, (void**)&pBuffer))
	    {
		bResult = FALSE;
	    }
            else
            {
                pBuffer->Set((audioData.pData)->GetBuffer(), 
                             ulSize - ulExtraInBytes);

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

                audioData.pData = pBuffer;
            }
	}
    }

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

    return bResult;
}

void CVorbisRenderer::updateBitrateInfo(vorbis_info* vi)
{
    if (vi && m_pStream && m_pContext)
    {
        INT32 clipBitrate = 0;

        if (vi->bitrate_nominal > 0)
        {
            clipBitrate = vi->bitrate_nominal;
        }
        else if (vi->bitrate_upper > 0)
        {
            clipBitrate = vi->bitrate_upper;
        }

        UINT32 uStreamRegID;
        IHXRegistry* pReg = NULL;
        if ((HXR_OK == getRegistryID(m_pStream, uStreamRegID)) &&
            (HXR_OK == m_pContext->QueryInterface(IID_IHXRegistry,
                                                  (void**)&pReg)))
        {
            CHXString propName;

            if ((clipBitrate > 0) &&
                (HXR_OK == getPropName(uStreamRegID, "ClipBandwidth",
                                       propName)))
            {
                pReg->SetIntByName(propName, clipBitrate);
            }
        }
        HX_RELEASE(pReg);
    }
}

void CVorbisRenderer::updateTACInfo(vorbis_comment* vc)
{
    if (vc && m_pStream && m_pContext)
    {
        IHXStreamSource* pStreamSrc = NULL;
        IHXRegistry* pReg = NULL;
        UINT32 uSourceRegID;

        if ((HXR_OK == m_pStream->GetSource(pStreamSrc)) &&
            (HXR_OK == getRegistryID(pStreamSrc, uSourceRegID)) &&
            (HXR_OK == m_pContext->QueryInterface(IID_IHXRegistry,
                                                  (void**)&pReg)))
        {
            int tagInfoCount = sizeof(zm_tagInfo) / sizeof(VorbisTagMapping);
            for (int i = 0; i < tagInfoCount; i++)
            {
                char* pVorbisTag = zm_tagInfo[i].m_pVorbisTag;
                const char* pPropName = zm_tagInfo[i].m_pPropName;

                int commentCount = vorbis_comment_query_count(vc, pVorbisTag);
                
                if (commentCount > 0)
                {
                    CHXString value = vorbis_comment_query(vc, pVorbisTag, 0);

                    for (int j = 1; j < commentCount; j++)
                    {
                        value += ", ";
                        value += vorbis_comment_query(vc, pVorbisTag, j);
                    }

                    IHXBuffer* pPropValBuf = NULL;
                    
                    if (HXR_OK == CreateStringBuffer(pPropValBuf, 
                                                     value,
                                                     m_pContext))
                    {
                        
                        CHXString propName;

                        if (HXR_OK == getPropName(uSourceRegID, pPropName,
                                                  propName))
                        {
                            pReg->SetStrByName(propName, pPropValBuf);
                        }
                    }
                    HX_RELEASE(pPropValBuf);
                }
            }
        }
        HX_RELEASE(pStreamSrc);
        HX_RELEASE(pReg);
    }
}

HX_RESULT 
CVorbisRenderer::getPropName(UINT32 uBaseID, const char* pChildPropName,
                             CHXString& propName)
{
    HX_RESULT res = HXR_INVALID_PARAMETER;

    if (!m_pContext)
    {
        res = HXR_UNEXPECTED;
    }
    else if (pChildPropName && strlen(pChildPropName))
    {
        IHXRegistry* pReg = NULL;
        IHXBuffer* pBasePropName = NULL;

        res = m_pContext->QueryInterface(IID_IHXRegistry, (void**)&pReg);
        
        if (HXR_OK == res)
        {
            res = pReg->GetPropName(uBaseID, pBasePropName);
        }

        if (HXR_OK == res)
        {
            propName = (const char*)pBasePropName->GetBuffer();
            propName += '.';
            propName += pChildPropName;
        }

        HX_RELEASE(pReg);
        HX_RELEASE(pBasePropName);
    }

    return res;
}

HX_RESULT 
CVorbisRenderer::getRegistryID(IUnknown* pUnk, REF(UINT32) uRegID)
{
    HX_RESULT res = HXR_INVALID_PARAMETER;

    if (pUnk)
    {
        IHXRegistryID* pRegID = NULL;

        res = pUnk->QueryInterface(IID_IHXRegistryID, (void**)&pRegID);

        if (HXR_OK == res)
        {
            res = pRegID->GetID(uRegID);
        }

        HX_RELEASE(pRegID);
    }

    return res;
}

void CVorbisRenderer::gotoInitialHeaderState()
{
    vorbis_comment_clear(&m_vc);
    vorbis_info_clear(&m_vi);
    
    vorbis_info_init(&m_vi);
    vorbis_comment_init(&m_vc);
    
    m_VorbisState = VorbisInitialHeader;
}

HX_RESULT 
CVorbisRenderer::handleInitialHeader(ogg_packet* pOp)
{
    HX_RESULT res = HXR_FAILED;

    if (vorbis_synthesis_headerin(&m_vi, &m_vc, pOp) >= 0) 
    {
        DPRINTF(D_VORBIS,("Got Initial Header\n"));
        
        updateBitrateInfo(&m_vi);
        
        /* check to see if the audio format is different
         * from the default values 
         */
        if ((m_audioFmt.uChannels != m_vi.channels) ||
            (m_audioFmt.ulSamplesPerSec != (UINT32)m_vi.rate))
        {
            /* The parameters are different. 
             * Release our handle on the current stream
             * and create a new audio stream object.
             */
            res = InitAudioStream(m_vi.rate, 
                                  m_vi.channels);
            
            m_bIsGapInStreaming = TRUE;
        }
        else
        {
            m_pCurrentStream->SetDryNotification(this);
            res = HXR_OK;
        }
        
        m_VorbisState = VorbisCommentHeader;
    } 
    else
    {
        HX_ASSERT(FALSE);
    }

    return res;
}
