/*
 * MPEGFrame.cpp
 * -------------
 * Purpose: Basic MPEG frame parsing functionality
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "MPEGFrame.h"
#include "mpt/io_read/filereader.hpp"
#include "../common/FileReader.h"

OPENMPT_NAMESPACE_BEGIN

// Samples per frame - for each MPEG version and all three layers
static constexpr uint16 samplesPerFrame[2][3] =
{
	{ 384, 1152, 1152 },	// MPEG 1
	{ 384, 1152,  576 }		// MPEG 2 / 2.5
};
// Bit rates for each MPEG version and all three layers
static constexpr uint16 bitRates[2][3][15] =
{
	// MPEG 1
	{
		{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 },	// Layer 1
		{ 0, 32, 48, 56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 384 },	// Layer 2
		{ 0, 32, 40, 48,  56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320 }	// Layer 3
	},
	// MPEG 2 / 2.5
	{
		{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 },	// Layer 1
		{ 0,  8, 16, 24, 32, 40, 48,  56,  64,  80,  96, 112, 128, 144, 160 },	// Layer 2
		{ 0,  8, 16, 24, 32, 40, 48,  56,  64,  80,  96, 112, 128, 144, 160 }	// Layer 3
	}
};
// Sampling rates for each MPEG version and all three layers
static constexpr uint16 samplingRates[4][3] =
{ 
	{ 11025, 12000,  8000 },	// MPEG 2.5
	{     0,     0,     0 },	// Invalid
	{ 22050, 24000, 16000 },	// MPEG 2
	{ 44100, 48000, 32000 }		// MPEG 1
};
// Samples per Frame / 8
static constexpr uint8 mpegCoefficients[2][3] =
{
	{ 12, 144, 144 },	// MPEG 1
	{ 12, 144,  72 }	// MPEG 2 / 2.5
};
// Side info size = Offset in frame where Xing/Info magic starts
static constexpr uint8 sideInfoSize[2][2] =
{
	{ 17, 32 },	// MPEG 1
	{  9, 17 }	// MPEG 2 / 2.5
};


static inline bool IsMPEGHeaderImpl(const uint8 * header)
{
	return header[0] == 0xFF && (header[1] & 0xE0) == 0xE0	// Sync
		&& (header[1] & 0x18) != 0x08	// Invalid MPEG version
		&& (header[1] & 0x06) != 0x00	// Invalid MPEG layer
		&& (header[2] & 0x0C) != 0x0C	// Invalid frequency
		&& (header[2] & 0xF0) != 0xF0;	// Invalid bitrate
}

template <std::size_t N>
static inline bool IsMPEGHeaderTemplate(const uint8 (&header)[N])
{
	static_assert(N >= 3);
	return IsMPEGHeaderImpl(header);
}

bool MPEGFrame::IsMPEGHeader(const uint8 (&header)[3])
{
	return IsMPEGHeaderTemplate(header);
}

bool MPEGFrame::IsMPEGHeader(const uint8 (&header)[4])
{
	return IsMPEGHeaderTemplate(header);
}


MPEGFrame::MPEGFrame(FileCursor &file)
	: frameSize(0)
	, numSamples(0)
	, isValid(false)
	, isLAME(false)
{
	uint8 header[4] = {};
	mpt::FR::ReadArray(file, header);
	
	if(!IsMPEGHeader(header))
	{
		return;
	}

	uint8 version = (header[1] & 0x18) >> 3;
	uint8 mpeg1 = (version == 3) ? 0 : 1;
	uint8 layer = 3 - ((header[1] & 0x06) >> 1);
	uint8 bitRate = (header[2] & 0xF0) >> 4;
	uint8 sampleRate = (header[2] & 0x0C) >> 2;
	uint8 padding = (header[2] & 0x02) >> 1;
	bool stereo = ((header[3] & 0xC0) >> 6) != 3;

	isValid = true;
	frameSize = static_cast<uint16>((((mpegCoefficients[mpeg1][layer] * (bitRates[mpeg1][layer][bitRate] * 1000) / samplingRates[version][sampleRate]) + padding)) * (layer == 0 ? uint16(4u) : uint16(1u)));
	numSamples = samplesPerFrame[mpeg1][layer];
	if(stereo)
	{
		numSamples *= 2u;
	}

	uint32 lameOffset = sideInfoSize[mpeg1][stereo ? 1 : 0];
	if(frameSize < lameOffset + 8)
	{
		return;
	}

	uint8 frame[36] = {};
	mpt::FR::ReadStructPartial(file, frame, lameOffset + 4);
	// Don't check first two bytes, might be CRC
	for(uint32 i = 2; i < lameOffset; i++)
	{
		if(frame[i] != 0)
		{
			return;
		}
	}

	// This is all we really need to know for our purposes in the MO3 decoder.
	isLAME = !std::memcmp(frame + lameOffset, "Info", 4) || !std::memcmp(frame + lameOffset, "Xing", 4);
}

OPENMPT_NAMESPACE_END
