///////////////////////////////////////////////////////////////////////////////
//
// File      : Asura_PS3_FMV_pamf.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_pamf.h"

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

// Asura PS3 headers
#include "FMV/Asura_PS3_FMV_dmux.h"

#include <cell/sysmodule.h>


////PGH/////////////////////////////////////////
// General constants (from pamf_dmux sample)
////////////////////////////////////////////////
const u_int VIDEO_FULL_RATE_BUFFER_NUM = 8;
const u_int VIDEO_HALF_RATE_BUFFER_NUM = 4;
const u_int AUDIO_BUFFER_SAMPLES       = 8000;

const u_int VDEC_LIB_M2V_SPU_NUM = 1;
const u_int VDEC_LIB_AVC_SPU_NUM = 4;


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_pamf::Asura_PS3_FMV_pamf
// Description : Initialise wrapper and load the pamf module
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
Asura_PS3_FMV_pamf::Asura_PS3_FMV_pamf()
{
	m_pWorkMemory = NULL;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_pamf::~Asura_PS3_FMV_pamf
// Description : Clean up after pamf library usage
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
Asura_PS3_FMV_pamf::~Asura_PS3_FMV_pamf()
{
	Close();
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_pamf::Open
// Description : Open the given file to examine for pamf information
//
// Parameters  : 
//   const char *szFilename - Name of file to examine
//
// Returns     : bool - Returns true on success
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_FMV_pamf::Open(const char *szFilename)
{
	int iResult;
	strncpy(m_szFilename, szFilename, ASURA_PS3_MAX_PATH);

	Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - Probing file: %s\n",  m_szFilename));

	uint8_t	aStreamBuffer[2048];
	if ( !Asura_PS3_FileSystem::OpenForReading(m_szFilename, &m_xFileHandle) )
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - Unable to open file %s\n", m_szFilename));
		return false;
	}

	// Get total file size
	m_uFileSize = Asura_PS3_FileSystem::GetFileSize( m_xFileHandle );
	Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - File size is %d bytes\n", m_uFileSize));

	// Read first few bytes	and parse header
	u_int uReadBytes = Asura_PS3_FileSystem::ReadData( m_xFileHandle, sizeof(aStreamBuffer), aStreamBuffer );
	if ( uReadBytes < sizeof(aStreamBuffer) )
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - File header too small\n"));
		goto labelError;
	}

	if ( Asura_PS3_FileSystem::SetCurrentPosition(m_xFileHandle, 0) < CELL_OK )
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - Can't re-seek to start of file\n"));
		goto labelError;
	}

	
	// Load the pamf module
	iResult = cellSysmoduleLoadModule(CELL_SYSMODULE_PAMF);
	if (iResult < CELL_OK)
	{
		Asura_Assert(false, "[Asura_PS3_FMV_pamf::Open] - Unable to load PAMF module\n");
		goto labelError;
	}
	
	// Get PAMF header size from file
	iResult = cellPamfGetHeaderSize(aStreamBuffer, m_uFileSize, &m_uHeaderSize);
	if ( iResult < CELL_OK )
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - cellPamfGetHeaderSize failed (error = %d)\n", iResult));
		goto labelError;
	}
	
	iResult = cellPamfGetStreamOffsetAndSize(aStreamBuffer, m_uFileSize, &m_uStreamOffset, &m_uStreamSize);
	if ( iResult < CELL_OK )
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - cellPamfGetStreamOffsetAndSizefailed (error = %d)\n", iResult));
		goto labelError;
	}
	
	Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - Stream offset=%d, size=%d\n", m_uStreamOffset, m_uStreamSize));

	m_pWorkMemory = reinterpret_cast<uint8_t*>( Asura_Allocate(m_uHeaderSize) );
	if ( m_pWorkMemory == NULL )  
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - Unable to allocate working memory\n"));
		goto labelError;
	}

	uReadBytes = Asura_PS3_FileSystem::ReadData(m_xFileHandle, m_uHeaderSize, m_pWorkMemory);
	if ( uReadBytes < m_uHeaderSize )
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - Error reading header\n"));
		goto labelError;
	}

	// Finally initialise the PAMF reader
	iResult = cellPamfReaderInitialize(&m_xReader, m_pWorkMemory, m_uFileSize, CELL_PAMF_INITIALIZE_VERIFY_ON);
	if (iResult < CELL_OK)
	{
		Asura_OutputToDebugger(("[Asura_PS3_FMV_pamf::Open] - cellPamfReaderInitialize failed (error = %d)\n", iResult));
		goto labelError;
	}

	return true;

// Exit after error with cleanup
labelError:
	Close();
	return false;
}


////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_pamf::Close
// Description : Close and clean up the pamf reader
//
// Parameters  : none
//
// Returns     : nothing
///////////////////////////////////////////////////////////////////////////////
void Asura_PS3_FMV_pamf::Close()
{
	// Close file if still open
	if ( m_xFileHandle != Asura_PS3_FileSystem::INVALID_HANDLE )
	{
		Asura_PS3_FileSystem::Close(&m_xFileHandle);
	}

	// Free memory if still allocated
	if ( m_pWorkMemory != NULL )
	{
		Asura_Deallocate(m_pWorkMemory);
		m_pWorkMemory = NULL;
	}
	
	// Unload the pamf module
	cellSysmoduleUnloadModule(CELL_SYSMODULE_PAMF);
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_pamf::GetVideoStreamInfo
// Description : Get specific information about the video stream 
//
// Parameters  : 
//   SPamfVideoStreamInfo* pVideoInfo - Video info structure to fill in
//
// Returns     : bool - Returns true on success, false otherwise
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_FMV_pamf::GetVideoStreamInfo(SPamfVideoStreamInfo* pVideoInfo)
{
	int iResult;
	uint8_t	streamNum = cellPamfReaderGetNumberOfSpecificStreams(&m_xReader, CELL_PAMF_STREAM_TYPE_VIDEO);
	if (streamNum == 0)
	{
		pVideoInfo->bPresent = false;
		return true;
	}

	pVideoInfo->bPresent = true;

	iResult = cellPamfReaderSetStreamWithTypeAndIndex(&m_xReader, CELL_PAMF_STREAM_TYPE_VIDEO, 0);
	if (iResult < CELL_OK)
	{
		return false;
	}

	uint8_t	channel;
	iResult = cellPamfReaderGetStreamTypeAndChannel(&m_xReader, &pVideoInfo->uType, &channel);
	if (iResult < CELL_OK)
	{
		return false;
	}

	CellPamfEsFilterId	filterId;
	iResult = cellPamfStreamTypeToEsFilterId(pVideoInfo->uType, channel, &filterId);
	if (iResult < CELL_OK)
	{
		return false;
	}
	
	pVideoInfo->esFilterId.filterIdMajor = filterId.filterIdMajor;
	pVideoInfo->esFilterId.filterIdMinor = filterId.filterIdMinor;
	pVideoInfo->esFilterId.supplementalInfo1 = filterId.supplementalInfo1;
	pVideoInfo->esFilterId.supplementalInfo2 = filterId.supplementalInfo2;

	bool	halfRate = false;
	switch (pVideoInfo->uType)
	{
		case CELL_PAMF_STREAM_TYPE_AVC:
		{
			CellPamfAvcInfo	avcInfo;
			iResult = cellPamfReaderGetStreamInfo(&m_xReader, &avcInfo, sizeof(CellPamfAvcInfo) );
			if (iResult < CELL_OK)
			{
				return false;
			}

			pVideoInfo->esSpecInfo.avc.level = avcInfo.levelIdc;

			pVideoInfo->uWidth  = avcInfo.horizontalSize;
			pVideoInfo->uHeight = avcInfo.verticalSize - (avcInfo.frameCropBottomOffset * 2 * (2 - avcInfo.frameMbsOnlyFlag));
			halfRate = ( avcInfo.frameMbsOnlyFlag && (avcInfo.frameRateInfo < CELL_PAMF_AVC_FRC_60000DIV1001) );
			pVideoInfo->vdecSpuNum = VDEC_LIB_AVC_SPU_NUM;
			pVideoInfo->szType = "AVC";
		}
		break;
		
		case CELL_PAMF_STREAM_TYPE_M2V:
		{
			CellPamfM2vInfo	m2vInfo;
			iResult = cellPamfReaderGetStreamInfo(&m_xReader, &m2vInfo, sizeof(CellPamfM2vInfo));
			if (iResult < CELL_OK)
			{
				return false;
			}

			uint32_t level;
			switch(m2vInfo.profileAndLevelIndication)
			{
				case CELL_PAMF_M2V_MP_ML: level = CELL_DMUX_PAMF_M2V_MP_ML; break;
				case CELL_PAMF_M2V_MP_HL: 
				default:
					level = CELL_DMUX_PAMF_M2V_MP_HL;
				break;
			}

			pVideoInfo->esSpecInfo.m2v.profileLevel = level;
			pVideoInfo->uWidth = m2vInfo.horizontalSizeValue;
			pVideoInfo->uHeight = m2vInfo.verticalSizeValue;
			halfRate = (m2vInfo.progressiveSequence && m2vInfo.frameRateInfo < CELL_PAMF_M2V_FRC_60000DIV1001);
			pVideoInfo->vdecSpuNum = VDEC_LIB_M2V_SPU_NUM;
			pVideoInfo->szType = "MPEG2 Video";
		}
		break;
	default:
		return false;
	}

	pVideoInfo->bufferNum = (halfRate) ? VIDEO_HALF_RATE_BUFFER_NUM : VIDEO_FULL_RATE_BUFFER_NUM;
	return true;
}



////PGH////////////////////////////////////////////////////////////////////////
// Function    : Asura_PS3_FMV_pamf::GetAudioStreamInfo
// Description : 
//
// Parameters  : 
//   SPamfAudioStreamInfo* pAudioInfo - Audio info structure to fill in
//   int iAudioStream - Index of the audio stream to play back
//
// Returns     : bool - Returns true on success, false otherwise
///////////////////////////////////////////////////////////////////////////////
bool Asura_PS3_FMV_pamf::GetAudioStreamInfo(SPamfAudioStreamInfo* pAudioInfo, int iAudioStream)
{
	int	iResult;
	uint8_t	streamNum = cellPamfReaderGetNumberOfSpecificStreams(&m_xReader, CELL_PAMF_STREAM_TYPE_AUDIO);
	if ((streamNum == 0) || (iAudioStream > streamNum))
	{
		pAudioInfo->bPresent = false;
		return true;
	}

	pAudioInfo->bPresent = true;

	iResult = cellPamfReaderSetStreamWithTypeAndIndex(&m_xReader,CELL_PAMF_STREAM_TYPE_AUDIO, iAudioStream);
	if (iResult < CELL_OK)
	{
		return false;
	}

	uint8_t	channel;
	iResult = cellPamfReaderGetStreamTypeAndChannel(&m_xReader,&pAudioInfo->uType, &channel);
	if (iResult < CELL_OK)
	{
		return false;
	}

	CellPamfEsFilterId	filterId;
	iResult = cellPamfStreamTypeToEsFilterId(pAudioInfo->uType, channel, &filterId);
	if (iResult < CELL_OK)
	{
		return false;
	}
	
	pAudioInfo->esFilterId.filterIdMajor = filterId.filterIdMajor;
	pAudioInfo->esFilterId.filterIdMinor = filterId.filterIdMinor;
	pAudioInfo->esFilterId.supplementalInfo1 = filterId.supplementalInfo1;
	pAudioInfo->esFilterId.supplementalInfo2 = filterId.supplementalInfo2;

	switch(pAudioInfo->uType)
	{
		case CELL_PAMF_STREAM_TYPE_ATRAC3PLUS:
		{
			CellPamfAtrac3plusInfo	at3plusInfo;
			iResult = cellPamfReaderGetStreamInfo(&m_xReader, &at3plusInfo, sizeof(at3plusInfo));
			if (iResult < CELL_OK)
			{
				return false;
			}

			if (at3plusInfo.samplingFrequency != CELL_PAMF_FS_48kHz)
			{
				return false;
			}

			pAudioInfo->esSpecInfo.at3plus.reserved1 = 0;
			pAudioInfo->uChannels = at3plusInfo.numberOfChannels;
			pAudioInfo->auSample = 2048;
			pAudioInfo->szType = "ATRAC3plus(TM)";
		}
		break;
		
		case CELL_PAMF_STREAM_TYPE_PAMF_LPCM:
		{
			CellPamfLpcmInfo	lpcmInfo;
			iResult = cellPamfReaderGetStreamInfo(&m_xReader, &lpcmInfo, sizeof(lpcmInfo));
			if (iResult < CELL_OK)
			{
				return false;
			}

			if (lpcmInfo.samplingFrequency != CELL_PAMF_FS_48kHz)
			{
				return false;
			}

			pAudioInfo->esSpecInfo.lpcm.samplingFreq = CELL_DMUX_PAMF_FS_48K;
			pAudioInfo->esSpecInfo.lpcm.numOfChannels = lpcmInfo.numberOfChannels;
			if (lpcmInfo.bitsPerSample == CELL_PAMF_BIT_LENGTH_24)
			{
				pAudioInfo->esSpecInfo.lpcm.bitsPerSample = CELL_DMUX_PAMF_BITS_PER_SAMPLE_24;
			}
			else if (lpcmInfo.bitsPerSample == CELL_PAMF_BIT_LENGTH_16)
			{
				pAudioInfo->esSpecInfo.lpcm.bitsPerSample = CELL_DMUX_PAMF_BITS_PER_SAMPLE_16;
			}
			else
			{
				return false;
			}

			pAudioInfo->uChannels = lpcmInfo.numberOfChannels;
			pAudioInfo->auSample = 240;
			pAudioInfo->szType = "LPCM";
		}
		break;
		
		case CELL_PAMF_STREAM_TYPE_AC3:
		{
			CellPamfAc3Info	ac3Info;
			iResult = cellPamfReaderGetStreamInfo(&m_xReader, &ac3Info,sizeof(ac3Info));
			if (iResult < CELL_OK)
			{
				return false;
			}

			if (ac3Info.samplingFrequency != CELL_PAMF_FS_48kHz)
			{
				return false;
			}

			pAudioInfo->esSpecInfo.ac3.reserved1 = 0;
			pAudioInfo->uChannels = ac3Info.numberOfChannels;
			pAudioInfo->auSample = 1536;
			pAudioInfo->szType = "Dolby(TM) Digital";
		}
		break;
		
	default:
		return false;
	}

	pAudioInfo->bufferNum = (AUDIO_BUFFER_SAMPLES + pAudioInfo->auSample - 1) / pAudioInfo->auSample;
	return true;
}
