/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: theoravidfmt.cpp,v 1.10.2.2 2004/11/24 18:29:04 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 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 "theoravidfmt.h"
#include "pckunpck.h"

#include "debug.h"
#define D_THEORA_VIDFMT 0 //0x4000000

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

static 
UINT32 gcd(UINT32 a, UINT32 b)
{
    // Compute the greatest common denominator
    while(b != 0)
    {
        UINT32 tmp = b;
        b = a % b;
        a = tmp;
    }

    return a;
}

CTheoraVideoFormat::CTheoraVideoFormat(IHXCommonClassFactory* pCCF, 
				       CVideoRenderer* pVidRend,
                                       IHXStream* pStream) :
    CVideoFormat(pCCF, pVidRend),
    m_state(stStart),
    m_ulFPSNum(0),
    m_ulFPSDenom(0),
    m_uAspectNum(0),
    m_uAspectDenom(0),
    m_pPhaseInfo(NULL),
    m_currentSerialNum(0),
    m_bNeedKeyframe(TRUE),
    m_pStream(pStream),
    m_pRegistry(NULL)
{
#ifdef _DEBUG
    debug_level() |= D_THEORA_VIDFMT;
#endif

    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::CTheoraVideoFormat()\n"));

    ogg_sync_init(&m_oy);
    ogg_stream_init(&m_os, m_currentSerialNum);

    theora_info_init(&m_ti);
    theora_comment_init(&m_tc);
    memset(&m_ts, 0, sizeof(m_ts));

    m_lastFrameSize.cx = 0;
    m_lastFrameSize.cy = 0;

    if (m_pStream)
    {
        m_pStream->AddRef();
    }
    
    if (pVidRend)
    {
        IUnknown* pContext = pVidRend->GetContext();

        if (pContext)
        {
            pContext->QueryInterface(IID_IHXRegistry, (void**)&m_pRegistry);
        }
    }
}

CTheoraVideoFormat::~CTheoraVideoFormat()
{
    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::~CTheoraVideoFormat()\n"));

    ogg_sync_clear(&m_oy);
    ogg_stream_clear(&m_os);

    theora_clear(&m_ts);
    theora_info_clear(&m_ti);
    theora_comment_clear(&m_tc);

    HX_VECTOR_DELETE(m_pPhaseInfo);
    HX_RELEASE(m_pStream);
    HX_RELEASE(m_pRegistry);
}

CMediaPacket* CTheoraVideoFormat::CreateAssembledPacket(IHXPacket* pCodecData)
{
    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::CreateAssembledPacket(%u)\n",
			      pCodecData));

    CMediaPacket* pRet = NULL;

    if (pCodecData && !pCodecData->IsLost())
    {
	IHXBuffer* pInputBuffer = pCodecData->GetBuffer();

	// Copy data into the ogg_sync_state object
	char* buffer = ogg_sync_buffer(&m_oy, pInputBuffer->GetSize());
	memcpy(buffer, pInputBuffer->GetBuffer(), pInputBuffer->GetSize());
	ogg_sync_wrote(&m_oy, pInputBuffer->GetSize());
	
	HX_RELEASE(pInputBuffer);

	ogg_page og;

	UINT32 ulFrameCount = 0;

	while (1 == ogg_sync_pageout(&m_oy, &og))
	{
	    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::CAP() : Got page\n"));
	    if (ogg_page_serialno(&og) != m_currentSerialNum)
	    {
		m_currentSerialNum = ogg_page_serialno(&og);
		ogg_stream_init(&m_os, m_currentSerialNum);
	    }
	    
	    if (ogg_stream_pagein(&m_os, &og) == 0)
	    {
		ogg_packet op;

		while(1 == ogg_stream_packetout(&m_os, &op))
		{
		    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::CAP() : Got packet\n"));
		    IHXBuffer* pPktBuf = OggPktToIHXBuffer(&op);

		    if (op.b_o_s)
		    {
			HandleIdentHdr(&op);
		    }

		    if (pPktBuf)
		    {
			BOOL bCreatePkt = TRUE;

			UINT32 ulTimestamp = pCodecData->GetTime();
			
			if (!IsHeader(&op))
			{
			    ulTimestamp += CalcTimestamp(ulFrameCount);

			    if (m_bNeedKeyframe)
			    {
				if (IsKeyframe(&op))
				{
				    m_bNeedKeyframe = FALSE;
				}
				else
				{
				    bCreatePkt = FALSE;
				}
			    }
			}

			DPRINTF(D_THEORA_VIDFMT, 
				("CTheoraVideoFormat::CAP() : PKT_TS %u FRAME_TS %u\n", 
				 pCodecData->GetTime(),
				 ulTimestamp));
			
			if (bCreatePkt)
			{
			    CMediaPacket* pMediaPkt = 
				new CMediaPacket(pPktBuf,
						 pPktBuf->GetBuffer(),
						 pPktBuf->GetSize(),
						 pPktBuf->GetSize(),
						 ulTimestamp,
						 MDPCKT_USES_IHXBUFFER_FLAG,
						 (void*)1);

			    if (pMediaPkt)
			    {
				DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::CAP() : adding packet\n"));
				ReturnAssembledPacket(pMediaPkt);
				pMediaPkt = NULL;
			    }
			}
			
			HX_RELEASE(pPktBuf);
		    }

		    ulFrameCount++;
		}
	    }
	}
    }

    return pRet;
}

static
void CopyPixelData(REF(UINT8*) pDest, char* pSrc, UINT32 width, UINT32 height, 
		   int stride)
{
    for (int i = 0; i < height; i++)
    {
	memcpy(pDest, pSrc, width);
	pSrc += stride;
	pDest += width;
    }
}

static
void CopyAndStretchX(REF(UINT8*) pDest, const char* pSrc, 
                     UINT32 uWidth, UINT32 uHeight, int stride,
                     UINT32 uAspectNum, UINT32 uAspectDen,
                     UINT32 uAdjustedWidth,
                     const UINT8* pPhaseInfo)
{
    for (UINT32 i = 0; i < uHeight; i++)
    {
        UINT32 uPhaseCt = 0;
        const UINT8* pDestEnd = pDest + uAdjustedWidth;
        const unsigned char* pCur = (const unsigned char*)pSrc;

        while (pDest < pDestEnd)
        {
            UINT32 uLeft = pCur[0];
            UINT32 uRight = pCur[1];

            if (uLeft < uRight)
            {
                UINT32 uDelta = uRight - uLeft;
                *pDest++ =  uLeft + ((uDelta * pPhaseInfo[uPhaseCt]) >> 8);
            }
            else if (uLeft > uRight)
            {
                UINT32 uDelta = uLeft - uRight;
                *pDest++ =  uLeft - ((uDelta * pPhaseInfo[uPhaseCt]) >> 8);
            }
            else
            {
                *pDest++ = (UINT8)uLeft;
            }

            HX_ASSERT(uAspectDen < uAspectNum);
            uPhaseCt += uAspectDen;

            while(uPhaseCt >= uAspectNum)
            {
                uPhaseCt -= uAspectNum;
                pCur++;
            }
        }

        pSrc += stride;
    }
}

static
void CopyAndStretchY(REF(UINT8*) pDest, const char* pSrc, 
                     UINT32 uWidth, UINT32 uHeight, int stride,
                     UINT32 uAspectNum, UINT32 uAspectDen,
                     UINT32 uAdjustedHeight,
                     const UINT8* pPhaseInfo)
{
    UINT32 uPhaseCt = 0;
    for (UINT32 i = 0; i < uAdjustedHeight; i++)
    {
        const unsigned char* pCur = (const unsigned char*)pSrc;
        
        for (UINT32 j = 0; j < uWidth; j++)
        {
            UINT32 uTop = *pCur;
            UINT32 uBottom = *(pCur + stride);

            if (uTop < uBottom)
            {
                UINT32 uDelta = uBottom - uTop;
                *pDest++ =  uTop + ((uDelta * pPhaseInfo[uPhaseCt]) >> 8);
            }
            else if (uTop > uBottom)
            {
                UINT32 uDelta = uTop - uBottom;
                *pDest++ =  uTop - ((uDelta * pPhaseInfo[uPhaseCt]) >> 8);
            }
            else
            {
                *pDest++ = (UINT8)uTop;
            }

            pCur++;
        }

        HX_ASSERT(uAspectNum < uAspectDen);
        uPhaseCt += uAspectNum;

        while(uPhaseCt >= uAspectDen)
        {
            uPhaseCt -= uAspectDen;
            pSrc += stride;
        }
    }
}

static 
void DestroySampleDesc(void* pSampleDesc, void* pUserData)
{
    HXxSize* pDims = (HXxSize*)pSampleDesc;
    delete pDims;
}

CMediaPacket* 
CTheoraVideoFormat::CreateDecodedPacket(CMediaPacket* pCodedPacket)
{
    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::CreateDecodedPacket(%p)\n",
			      pCodedPacket));

    CMediaPacket* pRet = NULL;
    
    if (pCodedPacket && pCodedPacket->m_pData)
    {
	yuv_buffer yuv;
	ogg_packet* pOp = (ogg_packet*)pCodedPacket->m_pData;

	DPRINTF(D_THEORA_VIDFMT, 
		("CTheoraVideoFormat::CreateDecodedPacket() : bytes %u\n",
		 pOp->bytes));
    
	int err = 0;

	if (pOp->b_o_s)
	{
            // Clear out old state
            theora_clear(&m_ts);
            theora_info_clear(&m_ti);
            theora_comment_clear(&m_tc);
            HX_VECTOR_DELETE(m_pPhaseInfo);

            // Prep for new state
            theora_info_init(&m_ti);
            theora_comment_init(&m_tc);
            memset(&m_ts, 0, sizeof(m_ts));

	    ChangeState(stNeedIdent);
	}
    
	switch(m_state) {
	case stNeedIdent:
	    if ( 0 == theora_decode_header(&m_ti, &m_tc, pOp))
	    {
                m_uAspectNum = m_ti.aspect_numerator;
                m_uAspectDenom = m_ti.aspect_denominator;
                
                if (m_uAspectNum && m_uAspectDenom &&
                    (m_uAspectNum != m_uAspectDenom))
                {
                    UINT32 tmp = gcd(m_uAspectNum, m_uAspectDenom);
                    if (tmp > 1)
                    {
                        m_uAspectNum /= tmp; 
                        m_uAspectDenom /= tmp;
                    }
                }
                else
                {
                    m_uAspectNum = 1;
                    m_uAspectDenom = 1;
                }

                updateBitrateInfo(&m_ti);

		ChangeState(stNeedComments);
	    }
	    break;
	case stNeedComments:
	    if ( 0 == theora_decode_header(&m_ti, &m_tc, pOp))
	    {
                updateTACInfo(&m_tc);
		ChangeState(stNeedCodebooks);
	    }
	    break;
	case stNeedCodebooks:
	    if ( 0 == theora_decode_header(&m_ti, &m_tc, pOp))
	    {
		theora_decode_init(&m_ts, &m_ti);
		ChangeState(stReady);
	    }
	    break;
	case stReady: {
	    theora_decode_packetin(&m_ts, pOp);
	    
	    err = theora_decode_YUVout(&m_ts, &yuv);
	    
	    if (0 == err)
	    {
		pRet = CreateYUVPacket(&m_ti, yuv, pCodedPacket->m_ulTime);
	    }

	    DPRINTF(D_THEORA_VIDFMT, ("YUVout err %d\n", err));
	    DPRINTF(D_THEORA_VIDFMT, ("YUVout y_width %d\n", yuv.y_width));
	    DPRINTF(D_THEORA_VIDFMT, ("YUVout y_height %d\n", yuv.y_height));
	    DPRINTF(D_THEORA_VIDFMT, ("YUVout y_stride %d\n", yuv.y_stride));
	    DPRINTF(D_THEORA_VIDFMT, ("YUVout uv_width %d\n", yuv.uv_width));
	    DPRINTF(D_THEORA_VIDFMT, ("YUVout uv_height %d\n", yuv.uv_height));
	    DPRINTF(D_THEORA_VIDFMT, ("YUVout uv_stride %d\n", yuv.uv_stride));
	} break;
	};
    }

    if (pCodedPacket != NULL)
    {
	pCodedPacket->Clear();
	delete pCodedPacket;
    }

    DPRINTF(D_THEORA_VIDFMT, 
	    ("CTheoraVideoFormat::CreateDecodedPacket() : pRet %p\n", pRet));

    return pRet;
}

HX_RESULT 
CTheoraVideoFormat::InitBitmapInfoHeader(HXBitmapInfoHeader &BitmapInfoHeader,
					 CMediaPacket* pVideoPacket)
{
    HX_RESULT res = HXR_FAILED;

    if (pVideoPacket && pVideoPacket->m_pSampleDesc)
    {
	HXxSize* pDims = (HXxSize*)pVideoPacket->m_pSampleDesc;
	BitmapInfoHeader.biWidth = pDims->cx;
	BitmapInfoHeader.biHeight = pDims->cy;
	BitmapInfoHeader.biSizeImage = (BitmapInfoHeader.biWidth * 
					BitmapInfoHeader.biHeight * 
					BitmapInfoHeader.biBitCount / 8);
	res = HXR_OK;
    }
    return res;
}

void CTheoraVideoFormat::Reset()
{
    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::Reset()\n"));
    m_bNeedKeyframe = TRUE;

    m_lastFrameSize.cx = 0;
    m_lastFrameSize.cy = 0;

    ogg_stream_reset(&m_os);
    ogg_sync_reset(&m_oy);

    CVideoFormat::Reset();
}

void CTheoraVideoFormat::Close()
{
    HX_RELEASE(m_pStream);
    HX_RELEASE(m_pRegistry);
}

IHXBuffer* CTheoraVideoFormat::OggPktToIHXBuffer(ogg_packet* pOp)
{
    UINT32 ulDataSize = sizeof(ogg_packet) + pOp->bytes;
    IHXBuffer* pRet = CreateBuffer(ulDataSize);

    if (pRet)
    {
	ogg_packet* pOggPkt = (ogg_packet*)pRet->GetBuffer();
	UINT8* pPktData = pRet->GetBuffer() + sizeof(ogg_packet);

	// Copy ogg_packet struct
	*pOggPkt = *pOp;
	pOggPkt->packet = pPktData;
	
	// Copy packet data
	memcpy(pOggPkt->packet, pOp->packet, pOp->bytes);
    }

    return pRet;
}

CMediaPacket* CTheoraVideoFormat::CreateYUVPacket(theora_info* ti,
                                                  yuv_buffer& yuv, 
						  UINT32 ulTimestamp)
{
    CMediaPacket* pRet = NULL;

    if (yuv.y_width && yuv.y_height &&
        (yuv.y_width == ti->width) &&
        (yuv.y_height == ti->height) &&
        (yuv.uv_width == ti->width / 2) &&
        (yuv.uv_height == ti->height / 2))
    {
        // We only handle 4:2:0 output from the decoder right now
	HXxSize* pDims = NULL;

        // The color converter only accepts frame widths that are
        // a multiple of 4. ti->frame_width should always be <=
        // ti->width and ti->width is always a multiple of 16.
        UINT32 uFrameWidth = (ti->frame_width + 3) & ~0x3;
        UINT32 uFrameHeight = ti->frame_height;
        UINT32 uAdjustedWidth = uFrameWidth;
        UINT32 uAdjustedHeight = uFrameHeight;

        UINT32 uXOffset = ti->offset_x;
        UINT32 uYOffset = ti->offset_y;
        
        BOOL bStrechX = FALSE;
        if (m_uAspectNum > m_uAspectDenom)
        {
            UINT32 uQ = uAdjustedWidth / m_uAspectDenom;
            UINT32 uR = uAdjustedWidth - uQ * m_uAspectDenom;
            uAdjustedWidth = (uQ * m_uAspectNum +
                              (uR * m_uAspectNum) / m_uAspectDenom);
            uAdjustedWidth =  (uAdjustedWidth + 3) &  ~0x3;
            bStrechX = TRUE;

            if (!m_pPhaseInfo)
            {
                CreatePhaseInfo(m_uAspectNum);
            }
        }
        else if (m_uAspectNum < m_uAspectDenom)
        {
            UINT32 uQ = uAdjustedHeight / m_uAspectNum;
            UINT32 uR = uAdjustedHeight - uQ * m_uAspectNum;
            uAdjustedHeight = (uQ * m_uAspectDenom +
                               (uR * m_uAspectDenom) / m_uAspectNum);

            if (!m_pPhaseInfo)
            {
                CreatePhaseInfo(m_uAspectDenom);
            }
        }

        UINT32 ulLumaSize = uAdjustedWidth * uAdjustedHeight;
        UINT32 ulChromaSize = ulLumaSize / 4;
        UINT32 ulFrameSize = ulLumaSize + 2 * ulChromaSize;

	if ((m_lastFrameSize.cx != uAdjustedWidth) ||
	    (m_lastFrameSize.cy != uAdjustedHeight))
	{            
	    pDims = new HXxSize;

	    if (pDims)
	    {
		DPRINTF(D_THEORA_VIDFMT, ("CreateYUVPacket() : Setting frame size\n"));

		m_lastFrameSize.cx = uAdjustedWidth;
		m_lastFrameSize.cy = uAdjustedHeight;
		
		*pDims = m_lastFrameSize;
	    }
	}

        pRet = GetMediaPacket(ulFrameSize, ulTimestamp, pDims);
	
	if (pRet)
	{
            UINT8* pData = pRet->m_pData;

            int lumaOffset = uXOffset + uYOffset * yuv.y_stride;
            int chromaOffset = (uXOffset / 2 + 
                                (uYOffset / 2) * yuv.uv_stride);
            
            if (m_pPhaseInfo)
            {
                // We are doing aspect ratio adjustment.

                if (bStrechX)
                {
                    // We are stretching in the X direction

                    // Copy and stretch Luma plane
                    CopyAndStretchX(pData, yuv.y + lumaOffset, 
                                    uFrameWidth, uFrameHeight, 
                                    yuv.y_stride,
                                    m_uAspectNum, m_uAspectDenom,
                                    uAdjustedWidth,
                                    m_pPhaseInfo);
                
                    // Copy and stretch first chroma plane
                    CopyAndStretchX(pData, yuv.u + chromaOffset, 
                                    uFrameWidth / 2, uFrameHeight / 2, 
                                    yuv.uv_stride,
                                    m_uAspectNum, m_uAspectDenom,
                                    uAdjustedWidth / 2,
                                    m_pPhaseInfo);
                
                    // Copy and stretch second chroma plane
                    CopyAndStretchX(pData, yuv.v + chromaOffset, 
                                    uFrameWidth / 2, uFrameHeight / 2, 
                                    yuv.uv_stride,
                                    m_uAspectNum, m_uAspectDenom,
                                    uAdjustedWidth / 2,
                                    m_pPhaseInfo);
                }
                else
                {
                    // We are stretching in the Y direction

                    // Copy and stretch Luma plane
                    CopyAndStretchY(pData, yuv.y + lumaOffset, 
                                    uFrameWidth, uFrameHeight, 
                                    yuv.y_stride,
                                    m_uAspectNum, m_uAspectDenom,
                                    uAdjustedHeight,
                                    m_pPhaseInfo);
                
                    // Copy and stretch first chroma plane
                    CopyAndStretchY(pData, yuv.u + chromaOffset, 
                                    uFrameWidth / 2, uFrameHeight / 2, 
                                    yuv.uv_stride,
                                    m_uAspectNum, m_uAspectDenom,
                                    uAdjustedHeight / 2,
                                    m_pPhaseInfo);
                
                    // Copy and stretch second chroma plane
                    CopyAndStretchY(pData, yuv.v + chromaOffset, 
                                    uFrameWidth / 2, uFrameHeight / 2, 
                                    yuv.uv_stride,
                                    m_uAspectNum, m_uAspectDenom,
                                    uAdjustedHeight / 2,
                                    m_pPhaseInfo);
                }
            }
            else
            {
                // Copy Luma plane
                CopyPixelData(pData, yuv.y + lumaOffset, 
                              uFrameWidth, uFrameHeight, 
                              yuv.y_stride);
                
                // Copy first chroma plane
                CopyPixelData(pData, yuv.u + chromaOffset, 
                              uFrameWidth / 2, uFrameHeight / 2, 
                              yuv.uv_stride);
                
                // Copy second chroma plane
                CopyPixelData(pData, yuv.v + chromaOffset, 
                              uFrameWidth / 2, uFrameHeight / 2, 
                              yuv.uv_stride);
            }
        }
	else
	{
	    HX_DELETE(pDims);
	}
    }

    return pRet;
}

IHXBuffer* CTheoraVideoFormat::CreateBuffer(UINT32 ulSize)
{
    IHXBuffer* pRet = NULL;
    
    if (m_pCommonClassFactory &&
	(HXR_OK == m_pCommonClassFactory->CreateInstance(IID_IHXBuffer,
							 (void**)&pRet)) &&
	(HXR_OK != pRet->SetSize(ulSize)))
    {
	HX_RELEASE(pRet);
    }

    return pRet;
}

BOOL CTheoraVideoFormat::IsHeader(ogg_packet* pOp) const
{
    BOOL bRet = FALSE;

    if (pOp && pOp->packet && (pOp->bytes >= 7) &&
	((pOp->packet[0] & 0x80) == 0x80) &&
	(!memcmp(pOp->packet + 1, "theora", 6)))
    {
	bRet = TRUE;
    }

    return bRet;
}

BOOL CTheoraVideoFormat::IsKeyframe(ogg_packet* pOp) const
{
    BOOL bRet = FALSE;

    if (pOp && pOp->packet && ((pOp->packet[0] & 0xc0) == 0))
    {
	bRet = TRUE;
    }

    return bRet;
}

void CTheoraVideoFormat::HandleIdentHdr(ogg_packet* pOp)
{
    if (IsHeader(pOp) && (pOp->bytes >= 42) && (pOp->packet[0] == 0x80))
    {
	const unsigned char* pIdent = (const unsigned char*)pOp->packet;
	
	// Ident Header
	m_ulFPSNum = ((pIdent[0x16] << 24) | (pIdent[0x17] << 16) |
		      (pIdent[0x18] << 8) | (pIdent[0x19]));
	m_ulFPSDenom = ((pIdent[0x1a] << 24) | (pIdent[0x1b] << 16) |
			(pIdent[0x1c] << 8) | (pIdent[0x1d]));

        // Attempt to reduce the numerator and denominator
        // by finding the greatest common denominator.
        UINT32 tmp = gcd(m_ulFPSNum, m_ulFPSDenom);
        if (tmp > 1)
        {
            m_ulFPSNum /= tmp;
            m_ulFPSDenom /= tmp;
        }
    }
}

UINT32 CTheoraVideoFormat::CalcTimestamp(UINT32 ulFrame) const
{
    UINT32 uRet = 0;

    if (m_ulFPSNum)
    {
        ogg_int64_t q = ulFrame / m_ulFPSNum;
        ogg_int64_t r = ulFrame - (q * m_ulFPSNum);
        ogg_int64_t mult = ((ogg_int64_t)m_ulFPSDenom) * 1000;
        ogg_int64_t ts = ((q * mult) + 
                          ((r * mult) / m_ulFPSNum));
        uRet = INT64_TO_UINT32(ts);
    }

    return uRet;
}

void CTheoraVideoFormat::ChangeState(State newState)
{
    DPRINTF(D_THEORA_VIDFMT, ("CTheoraVideoFormat::ChangeState() : %d -> %d\n",
			      m_state, newState));
    m_state = newState;
}

CMediaPacket* 
CTheoraVideoFormat::GetMediaPacket(UINT32 uFrameSize,
                                   UINT32 uTimestamp,
                                   HXxSize* pDims)
{
    CMediaPacket* pRet = (CMediaPacket*)m_pFramePool->Get(uFrameSize);
    
    if (pRet)
    {
        pRet->Init(pRet->m_pData, uFrameSize, uTimestamp, 0, pDims);
    }
    else
    {
        UCHAR* pData = new UCHAR[uFrameSize];
        
        if (pData)
        {
            pRet = new CMediaPacket(pData, 
                                    pData,
                                    uFrameSize,
                                    uFrameSize,
                                    uTimestamp,
                                    0,
                                    pDims /* desc */);
            if (!pRet)
            {
                HX_VECTOR_DELETE(pData);
            }
        }
    }

    if (pRet)
    {
        if (pDims)
        {
            pRet->SetSampleDescKiller(&DestroySampleDesc);
        }
    }

    return pRet;
}

void 
CTheoraVideoFormat::CreatePhaseInfo(UINT32 uPhaseCt)
{
    m_pPhaseInfo = new UINT8[uPhaseCt];

    if (m_pPhaseInfo)
    {
        for (UINT32 i = 0; i < uPhaseCt; i++)
        {
            m_pPhaseInfo[i] = (UINT8)MulDiv(256, i , uPhaseCt);
        }
    }
}

void CTheoraVideoFormat::updateBitrateInfo(theora_info* ti)
{
    if (ti && m_pStream && m_pRegistry)
    {
        INT32 clipBitrate = ti->target_bitrate;

        UINT32 uStreamRegID;
        if (HXR_OK == getRegistryID(m_pStream, uStreamRegID))
        {
            CHXString propName;

            if (HXR_OK == getPropName(uStreamRegID, "ClipBandwidth",
                                      propName))
            {
                m_pRegistry->SetIntByName(propName, clipBitrate);
            }
        }
    }
}

void CTheoraVideoFormat::updateTACInfo(theora_comment* tc)
{
    if (tc && m_pStream && m_pRegistry)
    {
        IHXStreamSource* pStreamSrc = NULL;
        UINT32 uSourceRegID;

        if ((HXR_OK == m_pStream->GetSource(pStreamSrc)) &&
            (HXR_OK == getRegistryID(pStreamSrc, uSourceRegID)))
        {
            int tagInfoCount = sizeof(zm_tagInfo) / sizeof(TheoraTagMapping);
            for (int i = 0; i < tagInfoCount; i++)
            {
                char* pTheoraTag = zm_tagInfo[i].m_pTheoraTag;
                const char* pPropName = zm_tagInfo[i].m_pPropName;

                int commentCount = theora_comment_query_count(tc, pTheoraTag);
                
                if (commentCount > 0)
                {
                    CHXString value = theora_comment_query(tc, pTheoraTag, 0);

                    for (int j = 1; j < commentCount; j++)
                    {
                        value += ", ";
                        value += theora_comment_query(tc, pTheoraTag, j);
                    }

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

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

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

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

        res = m_pRegistry->GetPropName(uBaseID, pBasePropName);

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

        HX_RELEASE(pBasePropName);
    }

    return res;
}

HX_RESULT 
CTheoraVideoFormat::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;
}
