// AcmCodec.cpp: implementation of the CAcmCodec class.
//
//////////////////////////////////////////////////////////////////////



#include "stdafx.h"
#include "AcmCodec.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CAcmCodec::CAcmCodec()
{
	m_pwfxCode = NULL;
	m_dwDstLength = 0;
	m_dwSrcLength = 0;
	m_bPass = FALSE;
	m_iRawSampleRate = 8000;
	m_iRawChannels = 1;
}

CAcmCodec::~CAcmCodec()
{

}

BOOL CAcmCodec::Init(int iType, BOOL bEncode)
{
	if (iType == ACM_CODEC_PASS)
	{
		m_bPass = TRUE;
		return TRUE;
	}
	
	m_bPass = FALSE;
	int iSize = GetMaxFormatSize();
	m_pwfxCode = (WAVEFORMATEX *)malloc(iSize);
	if (m_pwfxCode == NULL)		goto Error;
	memset(m_pwfxCode, 0, iSize);
	
	if (SetFormat(iType) == FALSE)		goto Error;

	if (OpenStream(bEncode) == FALSE)	goto Error;

	return TRUE;

Error:
	Free();
	return FALSE;
}

BOOL CAcmCodec::Init(WAVEFORMATEX * pWfx, int iFormatSize, BOOL bEncode)
{
	if (!NeedConvert(pWfx))
	{
		m_bPass = TRUE;
		return TRUE;
	}
	m_bPass = FALSE;
	m_pwfxCode = (WAVEFORMATEX *)malloc(iFormatSize);
	memcpy(m_pwfxCode, pWfx, iFormatSize);

	SetRawFormat();	
	if (OpenStream(bEncode) == FALSE)	goto Error;
	return TRUE;

Error:
	Free();
	return FALSE;
}

void CAcmCodec::Free()
{
	if (m_bPass)	return;

	if (acmStreamClose(m_has, 0) != 0)	
	{
		OutputDebugString("acmStreamClose(m_has) failed\n");
	}
	if (m_pwfxCode != NULL)
	{
		free(m_pwfxCode);
		m_pwfxCode = NULL;
	}
}

int CAcmCodec::Convert(unsigned char * pSrc, unsigned char * pDst)
{
	MMRESULT mmr;
	int iDataLength = 0;
	
	if (!PrepareHeader(pSrc, pDst))	
		goto Error;

	mmr = acmStreamConvert(m_has, &m_asHeader, ACM_STREAMCONVERTF_BLOCKALIGN);
    if (mmr != 0)
	{
		OutputDebugString("acmStreamConvert() failed\n");
		goto Error;
	}

	iDataLength = m_asHeader.cbDstLengthUsed;

	mmr = acmStreamUnprepareHeader(m_has, &m_asHeader, 0);
	if (mmr != 0)
	{
		OutputDebugString("CACMStream::Stop()::acmStreamUnprepareHeader failed\n");
		goto Error;
	}
	return iDataLength;

Error:
	return -1;
}


int CAcmCodec::Encode(char * pData, char * pCode, int iLength)
{
	if (m_bPass)
	{
		memcpy(pCode, pData, iLength);
		return iLength;
	}
	else
	{
		if (!GetStreamSize(iLength))	return -1;
		memset(pCode, 0, m_dwDstLength);
		return Convert((unsigned char *)pData, (unsigned char *)pCode);
	}
}

int CAcmCodec::Decode(char * pData, char * pCode, int iLength)
{
	return Encode(pCode, pData, iLength);
}

BOOL CAcmCodec::PrepareHeader(unsigned char * pSrc, unsigned char * pDst)
{
	MMRESULT mmr;

	memset(&m_asHeader, 0, sizeof(m_asHeader));
	m_asHeader.cbStruct = sizeof(m_asHeader);
	m_asHeader.cbSrcLength = m_dwSrcLength;
	m_asHeader.cbDstLength = m_dwDstLength;
	m_asHeader.pbSrc = pSrc;
	m_asHeader.pbDst = pDst;
	mmr = acmStreamPrepareHeader(m_has, &m_asHeader, 0);
	if (mmr != 0)
	{
		OutputDebugString("CACMStream::Convert()::acmStreamPrepareHeader() failed\n");
		if (mmr == MMSYSERR_INVALFLAG)
			OutputDebugString("At least one flag is invalid. \n");
		else if (mmr == MMSYSERR_INVALHANDLE)
			OutputDebugString("The specified handle is invalid. \n");
		else if (mmr == MMSYSERR_INVALPARAM)
			OutputDebugString("At least one parameter is invalid.\n");
		else if (mmr == MMSYSERR_NOMEM)
			OutputDebugString("The system is unable to allocate resources. \n");
		return FALSE;
	}
	return TRUE;
}

BOOL CAcmCodec::NeedConvert(WAVEFORMATEX * pWfx)
{
	if (pWfx->wFormatTag == WAVE_FORMAT_PCM && pWfx->nChannels == 1 &&
		pWfx->nSamplesPerSec == 8000 && pWfx->nAvgBytesPerSec == 16000 &&
		pWfx->nBlockAlign == 2 && pWfx->wBitsPerSample == 16 && pWfx->cbSize == 0)
		return FALSE;
	return TRUE;

}
void CAcmCodec::SetRawFormat()
{
	m_wfxRaw.wFormatTag = WAVE_FORMAT_PCM;   	 
	m_wfxRaw.nChannels = m_iRawChannels; // = 1;
	m_wfxRaw.nSamplesPerSec = m_iRawSampleRate; // = 8000;
	m_wfxRaw.nAvgBytesPerSec = m_iRawSampleRate * m_iRawChannels * 2;
	m_wfxRaw.nBlockAlign = m_iRawChannels * 2;
	m_wfxRaw.wBitsPerSample = 16;
	m_wfxRaw.cbSize = 0;

}

BOOL CAcmCodec::SetFormat(int iType)
{
	char *cbExtra;
	cbExtra = (char *)(m_pwfxCode) + sizeof(WAVEFORMATEX);

	SetRawFormat();

	m_pwfxCode->nChannels = 1;
	m_pwfxCode->nSamplesPerSec = 8000;
	switch (iType)
	{
	case ACM_CODEC_ALAW:
		m_pwfxCode->wFormatTag = WAVE_FORMAT_ALAW;
		m_pwfxCode->nAvgBytesPerSec = 8000;
		m_pwfxCode->nBlockAlign = 1;
		m_pwfxCode->wBitsPerSample = 8;
		m_pwfxCode->cbSize = 0;
		break;

	case ACM_CODEC_MULAW:
		m_pwfxCode->wFormatTag = WAVE_FORMAT_MULAW;
		m_pwfxCode->nAvgBytesPerSec = 8000;
		m_pwfxCode->nBlockAlign = 1;
		m_pwfxCode->wBitsPerSample = 8;
		m_pwfxCode->cbSize = 0;
		break;

	case ACM_CODEC_ADPCM:
		m_pwfxCode->wFormatTag = WAVE_FORMAT_ADPCM;
		m_pwfxCode->nAvgBytesPerSec = 4096;
		m_pwfxCode->nBlockAlign = 256;
		m_pwfxCode->wBitsPerSample = 4;
		m_pwfxCode->cbSize = 32;
		cbExtra[0] = -12;	cbExtra[1] = 1;		cbExtra[2] = 7;
		cbExtra[5] = 1;		cbExtra[9] = 2;		cbExtra[11] = -1;
		cbExtra[16] = -64;	cbExtra[18] = 64;	cbExtra[20] = -16;
		cbExtra[24] = -52;	cbExtra[25] = 1;	cbExtra[26] = 48;
		cbExtra[27] = -1;	cbExtra[28] = -120;	cbExtra[29] = 1;
		cbExtra[30] = 24;	cbExtra[31] = -1;
		break;

	case ACM_CODEC_GSM610:
		m_pwfxCode->wFormatTag = WAVE_FORMAT_GSM610;
		m_pwfxCode->nAvgBytesPerSec = 1625;
		m_pwfxCode->nBlockAlign = 65;
		m_pwfxCode->wBitsPerSample = 0;
		m_pwfxCode->cbSize = 2;
		cbExtra[0] = 64;
		cbExtra[1] = 1;
		break;

	case ACM_CODEC_G729CP:
		m_pwfxCode->wFormatTag = WAVE_FORMAT_GFDVR;
		m_pwfxCode->nAvgBytesPerSec = 1100;
		m_pwfxCode->nBlockAlign = 11;
		m_pwfxCode->wBitsPerSample = 0;
		m_pwfxCode->cbSize = 0;
		break;

	case ACM_CODEC_G723_1:
		m_pwfxCode->wFormatTag = 66;
		m_pwfxCode->nAvgBytesPerSec = 800;
		m_pwfxCode->nBlockAlign = 24;
		m_pwfxCode->wBitsPerSample = 0;
		m_pwfxCode->cbSize = 10;

		cbExtra[0] = 2; cbExtra[1] = 0; cbExtra[2] = (char)0xce;
		cbExtra[3] = (char)0x9a; cbExtra[4] = (char)0x32; cbExtra[5] = (char)0xf7;
		cbExtra[6] = (char)0xa2; cbExtra[7] = (char)0xae; cbExtra[8] = (char)0xde;
		cbExtra[9] = (char)0xac;
		break;

	default:
		OutputDebugString("format not supported!\n");
		return FALSE;
	}
	return TRUE;
}

int CAcmCodec::GetMaxFormatSize()
{
	ACMFORMATTAGDETAILS aftd;
	MMRESULT mmr;

	aftd.cbStruct = sizeof(aftd);
	aftd.dwFormatTag = WAVE_FORMAT_UNKNOWN;
	aftd.dwFormatTagIndex = 0;
	mmr = acmFormatTagDetails(NULL, &aftd, ACM_FORMATTAGDETAILSF_LARGESTSIZE);               
	if (mmr != 0)
	{
		OutputDebugString("CAcmCodec: GetMaxFormatSize failed, assure value 256\n");
		return 256;
	}
	return aftd.cbFormatSize;
}

BOOL CAcmCodec::OpenStream(BOOL bEncode)
{
	MMRESULT mmr;
	WAVEFORMATEX *	pSrcWfx;
	WAVEFORMATEX *	pDstWfx;

	if (bEncode)
	{
		pSrcWfx = &m_wfxRaw;
		pDstWfx = m_pwfxCode;
	}
	else
	{
		pDstWfx = &m_wfxRaw;
		pSrcWfx = m_pwfxCode;
	}
	mmr = acmStreamOpen(&m_has, NULL, pSrcWfx, pDstWfx, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);          
	if (mmr != 0)
	{
		switch (mmr)
		{
		case ACMERR_NOTPOSSIBLE:
			OutputDebugString("The requested operation cannot be performed. \n");
			break;

		case MMSYSERR_INVALFLAG:
			OutputDebugString("At least one flag is invalid. \n");
			break;

		case MMSYSERR_INVALHANDLE:
			OutputDebugString("The specified handle is invalid. \n");
			break;

		case MMSYSERR_INVALPARAM:
			OutputDebugString("At least one parameter is invalid. \n");
			break;

		case MMSYSERR_NOMEM:
			OutputDebugString("The system is unable to allocate resources. \n");
			break;
		}
		return FALSE;
	}
	return TRUE;
}

int CAcmCodec::GetMaxCodeLength(int iSrcLength)
{
	if (m_bPass)	return iSrcLength;

	if (GetStreamSize(iSrcLength))
		return m_dwDstLength;	
	else 
		return -1;
}

BOOL CAcmCodec::GetStreamSize(int iLength)
{
	MMRESULT mmr;

	m_dwSrcLength = iLength;
	mmr = acmStreamSize(m_has, m_dwSrcLength, &m_dwDstLength, ACM_STREAMSIZEF_SOURCE);  
	if (mmr != 0)
	{
		OutputDebugString("acmStreamSize() failed\n");
		return FALSE;
	}

	return TRUE;
}

