///////////////////////////////////////////////////////////////////////////////
//
// File      : Asura_PS3_FMV_vdec.cpp
// Author    : Philip Hall
// Created   : 18/10/2007
//
// Copyright (C) 2007 Rebellion. All Rights Reserved
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header
#include "FMV/Asura_PS3_FMV_vdec.h"

// Asura headers
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_LogFile.h"
#include "Rendering/Asura_PS3_VideoMemoryManager.h"


#include <cell/sysmodule.h>
#include <sdk_version.h>

#if CELL_SDK_VERSION < 0x240000
#include <ppu_thread.h>
#endif

////PGH/////////////////////////////////////////
// General constants (from pamf_dmux sample)
////////////////////////////////////////////////
const uint32_t  VDEC_LIB_PPU_THREAD_PRIO = 400;
const size_t    VDEC_LIB_PPU_STACK_SIZE  = 16384;
const uint32_t  VDEC_LIB_SPU_THREAD_PRIO = 145;
const u_int     VDEC_VIDEO_BUFFER_COUNT  = 4;


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::Asura_PS3_FMV_vdec
// Description : Initialise vdec video decoder wrapper 
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
Asura_PS3_FMV_vdec::Asura_PS3_FMV_vdec()
{
	m_pControl     = NULL;
	m_uMemSize 	   = 0;
	m_pWorkMemory  = NULL;
	m_uPRXModule   = 0;
	m_decHandle    = NULL;
	m_uFrameSizeBytes = 0;
	m_pAUInfo         = NULL;
	m_iRunningFrameCount = 0;
	m_pVideoBufferMemory = NULL;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::~Asura_PS3_FMV_vdec
// Description : Clean up the video decoder (& unload module)
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
Asura_PS3_FMV_vdec::~Asura_PS3_FMV_vdec()
{
	Close();
}


void Asura_PS3_FMV_vdec::Initialise(Asura_PS3_FMV_Control* pControl)
{
	m_pControl = pControl;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::Open
// Description : Open the video decoder ready for processing the given 
//               elementary stream (originating from the demuxer)
//
// Parameters  : 
//   Asura_PS3_FMV_ESInfo* pESInfo  - Source elementary stream
//   u_int uWidth          - Width of video frame images
//   u_int uHeight         - Height of video frame images
//   u_int uSPUCount       - Number of SPUs required to decode video
//
// Returns     : bool - Returns true on success, false otherwise
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_FMV_vdec::Open(Asura_PS3_FMV_ESInfo* pESInfo, u_int uWidth, u_int uHeight, u_int uSPUCount)
{
	m_pESInfo = pESInfo;

	// Set up for video buffering
	if ( !m_xVideoBuffer.Create(VDEC_VIDEO_BUFFER_COUNT) )
	{
		Asura_Assert(false, "[Asura_PS3_FMV_vdec::Open] - Unable to allocate video ring buffer\n");
		return false;
	}

	// Allocate VDEC_VIDEO_BUFFER_COUNT number of video buffers
	m_uFrameSizeBytes =	uWidth * uHeight * 4;  // ARGB (1 byte per component)
	u_int uBufferSize = ASURA_SIZE_TO_128_BYTE(m_uFrameSizeBytes) * VDEC_VIDEO_BUFFER_COUNT;

	m_pVideoBufferMemory = reinterpret_cast<u_char*>( ASURA_PS3_VIDEOMEMORY_ALLOCMAIN( uBufferSize, ASURA_ALIGN_128_BYTE ) );
	if ( m_pVideoBufferMemory == NULL )
	{
		Asura_Assert(false, "[Asura_PS3_FMV_vdec::Open] - Unable to allocate buffer for video frames\n");
		return false;
	}

	Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - %ux%u video buffering %u frames in %uKb\n", uWidth, uHeight, VDEC_VIDEO_BUFFER_COUNT, uBufferSize/1024));

	// Initialise vdec video decoder
	int iResult;
	CellVdecCb cb  = { &Asura_PS3_FMV_vdec::CallbackProxy, this };

	static const u_int auM2vLevel[] = { CELL_VDEC_MPEG2_MP_LL, CELL_VDEC_MPEG2_MP_ML,
                                         CELL_VDEC_MPEG2_MP_H14, CELL_VDEC_MPEG2_MP_HL };
	switch (pESInfo->uStreamType)
	{
		case CELL_PAMF_STREAM_TYPE_M2V:
			Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - Stream is MPEG 2\n"));
			m_uPRXModule			= CELL_SYSMODULE_VDEC_MPEG2;
			m_decType.codecType		= CELL_VDEC_CODEC_TYPE_MPEG2;
			m_decType.profileLevel	= auM2vLevel[pESInfo->xSpecInfo.m2v.profileLevel];
		break;
		
		case CELL_PAMF_STREAM_TYPE_AVC:
			Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - Stream is AVC\n"));
			m_uPRXModule			= CELL_SYSMODULE_VDEC_AVC;
			m_decType.codecType		= CELL_VDEC_CODEC_TYPE_AVC;
			m_decType.profileLevel	= pESInfo->xSpecInfo.avc.level;
		break;
		
		default:
		{
			Asura_Assert(false, "[Asura_PS3_FMV_vdec::Open] - Don't understand this video stream type\n");
			return false;
		}
	}
	
	iResult = cellSysmoduleLoadModule(m_uPRXModule);
	if (iResult < CELL_OK)
	{
		Asura_Assertf(false,("[Asura_PS3_FMV_vdec::Open] - cellSysmoduleLoadModule failed (error = %X)\n", iResult));
		return false;
	}
	

	CellVdecAttr xAttr;
	iResult = cellVdecQueryAttr(&m_decType, &xAttr);
	if (iResult < CELL_OK)
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - cellVdecQueryAttr failed (error = %X)\n", iResult));
		goto labelError;
	}
	
	m_uMemSize = xAttr.memSize;
	m_pWorkMemory = Asura_Allocate(m_uMemSize);
	if (m_pWorkMemory == NULL)
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - Unable to allocate working memory\n"));
		goto labelError;
	}
	
	// Open the correct video decoder
	CellVdecResource xResource;
	xResource.memAddr             = m_pWorkMemory;
	xResource.memSize             = m_uMemSize;
	xResource.ppuThreadPriority   = VDEC_LIB_PPU_THREAD_PRIO;
	xResource.ppuThreadStackSize  = VDEC_LIB_PPU_STACK_SIZE;
	xResource.spuThreadPriority   = VDEC_LIB_SPU_THREAD_PRIO;
	xResource.numOfSpus           = uSPUCount;
	
	Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - Opening video decoder\n"));
	iResult = cellVdecOpen(&m_decType, &xResource, &cb, &m_decHandle);
	if (iResult < CELL_OK)
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - cellVdecOpen failed (error = %X)\n", iResult));
		goto labelError;
	}
	
	iResult = cellVdecStartSeq(m_decHandle);
	if (iResult < CELL_OK) 
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Open] - cellVdecStartSeq failed (error = %X)\n", iResult));
		return iResult;
	}

	return true;

labelError:
	Close();
	return false;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::Close
// Description : Clean up vdec wrapper and unload the video decoder module
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_FMV_vdec::Close()
{
	m_xVideoBuffer.Destroy();
	if ( m_pVideoBufferMemory != NULL )
	{
		Asura_PS3_VideoMemoryManager::FreeMain( m_pVideoBufferMemory );
		m_pVideoBufferMemory = NULL;
	}

	if (m_decHandle)
	{
		int iResult = cellVdecClose(m_decHandle);
		if (iResult < CELL_OK)
		{
			Asura_Assertf(false, ("[Asura_PS3_FMV_vdec::Close] - cellVdecClose failed (error = %X)\n", iResult));
		}
		m_decHandle = NULL;
	}

	// Free allocated working memory 
	if ( m_pWorkMemory != NULL )
	{
		Asura_Deallocate(m_pWorkMemory);
		m_pWorkMemory = NULL;
	}

	// Unload PRX module
	if ( m_uPRXModule != 0 )
	{
		cellSysmoduleUnloadModule(m_uPRXModule);
		m_uPRXModule = 0;
	}
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::StartProcessing
// Description : Start processing the next video block 
//
// Parameters  : 
//
// Returns     : Asura_PS3_FMV_DecodeResult - Decoding result
///////////////////////////////////////////////////////////////////////////////
Asura_PS3_FMV_DecodeResult Asura_PS3_FMV_vdec::StartProcessing()
{
	// Release any completed video AUs
	if ( m_pESInfo->xAUDone.TryWait() )
	{
		cellDmuxReleaseAu(m_pESInfo->xHandle);
	}

	// Self buffer the frames when they appear (if there's room)
	if ( !m_xVideoBuffer.IsFull() )
	{
		if ( m_pESInfo->xReady.TryWait() )
		{
			GetNextFrame();
		}
	}

	// If there are any new AUs then try to process them
	if ( m_pAUInfo == NULL )
	{
		void* pAuSpecificInfo;
		int iResult = cellDmuxGetAu(m_pESInfo->xHandle, &m_pAUInfo, &pAuSpecificInfo);
		switch ( iResult )
		{
			case CELL_OK: break;

			case CELL_DMUX_ERROR_EMPTY:
			{
				return ASURA_PS3_FMV_DECODE_EMPTY;	
			}

            default: break;
		}
	}

	if (m_pAUInfo != NULL) 
	{
		CellVdecDecodeMode	mode = CELL_VDEC_DEC_MODE_NORMAL;
		CellVdecAuInfo		au = { m_pAUInfo->auAddr, m_pAUInfo->auSize,
									{ m_pAUInfo->ptsUpper, m_pAUInfo->ptsLower },
									{ m_pAUInfo->dtsUpper, m_pAUInfo->dtsLower }, 0, 0 };
		int iResult = cellVdecDecodeAu(m_decHandle, mode, &au);
		switch(iResult)
		{
			case CELL_OK:
				m_pAUInfo = NULL;
				if ( m_pESInfo->Lock() )
				{
					m_pESInfo->iDMUXFoundAUCount--; 
					m_pESInfo->Unlock();
				}
			break;

			case CELL_VDEC_ERROR_BUSY:
			break;

			default:
				m_pControl->Abort(RET_CODE_ERR_UNKNOWN);
			break;
		}		
	}

	return (m_pAUInfo == NULL) ? ASURA_PS3_FMV_DECODE_OK : ASURA_PS3_FMV_DECODE_BUSY;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::GetFrame
// Description : Add the frame to the buffer
//
// Parameters  : none
//
// Returns     : bool - Retrieved the next frame
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_FMV_vdec::GetNextFrame()
{
	// Query video information
	const CellVdecPicItem* pPicItem;
	const CellVdecPicFormat	xFormat = { CELL_VDEC_PICFMT_ARGB32_ILV, CELL_VDEC_COLOR_MATRIX_TYPE_BT709, 0xFF };
	cellVdecGetPicItem(m_decHandle, &pPicItem);

	Asura_PS3_VideoBlock *pBlock = m_xVideoBuffer.BeginAlloc();

	// Destroy frame if skipping or there's nowhere to put it
	if ( (pBlock == NULL) || (pPicItem->attr == CELL_VDEC_PICITEM_ATTR_SKIPPED) )
	{
		cellVdecGetPicture(m_decHandle, &xFormat, NULL);
	}
	// Retrieve frame otherwise
	else
	{
		if ( m_xBufferLock.Lock(0) )
		{
			pBlock->uSize = m_uFrameSizeBytes;
			pBlock->xTimestamp = pPicItem->auPts[0];
			pBlock->pFrame = m_pVideoBufferMemory + ASURA_SIZE_TO_128_BYTE(m_uFrameSizeBytes) * m_xVideoBuffer.GetWritePos();
			
			cellVdecGetPicture(m_decHandle, &xFormat, pBlock->pFrame);
			m_xVideoBuffer.EndAlloc();

			m_xBufferLock.Unlock();
		}
	}

	return true;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::GetFrameRate
// Description : Extract the frame rate (in frames per second)
//
// Parameters  : 
//   const CellVdecPicItem *pPicItem - 
//
// Returns     : u_int - Frame rate in FPS 
///////////////////////////////////////////////////////////////////////////////
u_int Asura_PS3_FMV_vdec::GetFrameRate(const CellVdecPicItem *pPicItem )
{
	if ( pPicItem->codecType == CELL_VDEC_CODEC_TYPE_MPEG2 )
	{
		switch ( ((CellVdecMpeg2Info*)(pPicItem->picInfo))->frame_rate_code )
		{
			case CELL_VDEC_MPEG2_FRC_24000DIV1001:
			case CELL_VDEC_MPEG2_FRC_24:
				return 24;

			case CELL_VDEC_MPEG2_FRC_25:
			case CELL_VDEC_MPEG2_FRC_30000DIV1001:
			case CELL_VDEC_MPEG2_FRC_30:
				return 30;

			case CELL_VDEC_MPEG2_FRC_50:
			case CELL_VDEC_MPEG2_FRC_60000DIV1001:
			case CELL_VDEC_MPEG2_FRC_60:
				return 60;
		}
	}
	return 0;
}

////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_vdec::Callback
// Description : Video decoder AU decoding callback
//
// Parameters  : 
//   CellVdecHandle handle    - Handle to the adec audio decoder
//   CellVdecMsgType msgType  - Video decoder message
//   int32_t msgData          - Message specific parameters
//
// Returns     : uint32_t - Result code (always 0)
///////////////////////////////////////////////////////////////////////////////
uint32_t Asura_PS3_FMV_vdec::Callback(CellVdecHandle /*handle*/, CellVdecMsgType msgType, int32_t msgData)
{
	switch( msgType )
	{
		case CELL_VDEC_MSG_TYPE_AUDONE:
			m_pESInfo->xAUDone.Signal();
			if ( msgData != CELL_OK )
			{
				Asura_OutputToDebugger(("    VDEC Faulty frame\n"));
			}
		break;

		case CELL_VDEC_MSG_TYPE_PICOUT:
		{
			//Asura_OutputToDebugger(("    VDEC Image Frame %d\n", m_iRunningFrameCount++));
			m_pESInfo->xReady.Signal();
		}
		break;

		case CELL_VDEC_MSG_TYPE_SEQDONE:
		{
			Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Callback] - VDEC Sequence completed\n"));
			m_pControl->EndVideoData();
		}
		break;

		case CELL_VDEC_MSG_TYPE_ERROR:
			Asura_OutputToDebugger(("[Asura_PS3_FMV_vdec::Callback] - VDEC fatal error 0x%X\n", msgData));
			m_pControl->Abort(RET_CODE_ERR_FATAL);
		break;

		default:
			Asura_Assert(false, "[Asura_PS3_FMV_adec::Callback] - Unhandled callback message type\n");
			m_pControl->Abort(RET_CODE_ERR_FATAL);
		break;
	}


	return 0;
}




