// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include "parsers.h"
#include "constants.h"

//
// Brands.
// Some (most) brands have Release information - e.g. 3gp6, 3gp5 etc.
// Therefore, to match the brand, we only look at the characters that don't
// represent Release information (in the above case, the 4th character).
// The Release character is set to zero.
//
static const TUint32 KMP4Brand = MAKE_INT32('m', 'p', '4', 0);
static const TUint32 K3GPBrand = MAKE_INT32('3', 'g', 'p', 0);
static const TUint32 K3G2Brand = MAKE_INT32('3', 'g', '2', 0);
static const TUint32 K3GSBrand = MAKE_INT32('3', 'g', 's', 0);	// Streaming servers.
static const TUint32 K3GRBrand = MAKE_INT32('3', 'g', 'r', 0);	// Progresive download and MMS.
static const TUint32 KQTBrand  = MAKE_INT32('q', 't', ' ', ' '); // for quicktime
//
// Box identifiers.
//
static const TUint32 KFtyp = MAKE_INT32('f', 't', 'y', 'p');
static const TUint32 KMoov = MAKE_INT32('m', 'o', 'o', 'v');
static const TUint32 KTrak = MAKE_INT32('t', 'r', 'a', 'k');
static const TUint32 KTkhd = MAKE_INT32('t', 'k', 'h', 'd');
static const TUint32 KMdia = MAKE_INT32('m', 'd', 'i', 'a');
static const TUint32 KHdlr = MAKE_INT32('h', 'd', 'l', 'r');
static const TUint32 KVide = MAKE_INT32('v', 'i', 'd', 'e');
static const TUint32 KUuid = MAKE_INT32('u', 'u', 'i', 'd');


//
// This truth table maps the following flags to a confidence level.
//
// A - trak box present
// B - moov box present
//
// A B -> Confidence
// =================
// 0 0 -> EPossible
// 0 1 -> EProbable
// 1 0 -> EProbable
// 1 1 -> ECertain
//
static const TInt KMPEG4FlagsToConfidence[] =
	{
	KConfPossible,
	KConfProbable,
	KConfProbable,
	KConfCertain,
	};


#define KMPEG4ConfidenceMask	0x03	// 00000011
#define KMPEG4BoxTitleLen		4
#define KMPEG4TRAKBit			KBit0	// 00000001
#define KMPEG4MOOVBit			KBit1	// 00000010
#define KMPEG4VideoBit			KBit2	// 00000100

static const TInt KMPEG4BoxIntroLen = 8;

//
// In order to find out the type of MPEG4 file it is
// we need to be able to map known extensions, expected
// ftyp expressions with MIME-types.
//
typedef struct
	{
	const TText* iExt;
	TUint32 iBrand;
	const TText8* iAudioMime;
	const TText8* iVideoMime;
	}
TMPEG4File;


//
// A table of ftyp's, extensions and mime-types.
//
// .mp4 - can contain either audio or video.
// .m4a - should contain (unprotected) audio only.
// .m4p - should contain protected audio only.
// .3gp - can contain either audio or video.
//
static const TMPEG4File KMPEG4Files[] =
	{
		{KExtMP4,	KMP4Brand,	KMimeMP4_A,	KMimeMP4_V},
		{KExt3GP,	K3GPBrand,	KMime3GP_A,	KMime3GP_V},
		{KExtM4A,	KMP4Brand,	KMimeMP4_A,	NULL},
		{KExt3G2,	K3G2Brand,	KMime3G2_A,	KMime3G2_V},
		{KExt3GP,	K3GSBrand,	KMime3GP_A,	KMime3GP_V},
		{KExt3GP,	K3GRBrand,	KMime3GP_A,	KMime3GP_V},
		{KExt3GA,	K3GPBrand,	KMime3GA,	NULL},
		{KExtMOV,   KQTBrand,   NULL, KMimeQuickV} // this entry is for .mov files
	};

static const TInt KMPEG4FileTypeCount = sizeof(KMPEG4Files) / sizeof(TMPEG4File);


//
//
//
TMPEG4Parser::TMPEG4Parser(CReader& aReader, TFlags& aFlags)
 :	iBrandIndex(KErrNotFound),
 	iIsFinished(EFalse),
 	iReader(aReader),
 	iFlags(aFlags),
 	iVideoAssumed(EFalse)
	{
	}


//
// Compare a brand with the ones this recogniser knows about.
//
TInt TMPEG4Parser::IsCompatibleBrand(TUint32 aBrand, TInt aStartPos)
	{
	for (TInt i = aStartPos; i < KMPEG4FileTypeCount; i++)
		{
		TUint32 knownBrand = KMPEG4Files[i].iBrand;
		if ((aBrand & knownBrand) == knownBrand)
			{
			return i;
			}
		}

	return KErrNotFound;
	}


//
// Try to determine the mime-type from the extension, and
// if that isn't matched, from the FTYP field if present.
// If none of these are matched, NULL is returned and the
// file isn't a valid MPEG4 file.
//
const TText8* TMPEG4Parser::MatchFileType(const TDesC& aExt)
	{
	TBool videoFound = iFlags.GetBitField(KMPEG4VideoBit);
	TBool video = (videoFound || iVideoAssumed);
	
	// Try to match the extension.
	if (aExt.Length() > 0)
		{
		for (TInt i = 0; i < KMPEG4FileTypeCount; i++)
			{
			TPtrC ext(KMPEG4Files[i].iExt);
			if (aExt.MatchF(ext) != KErrNotFound)
				{
				// Extension match. If the extension is for an audio-only format
				// we must make sure there is no video content in the file. If
				// video content is present then ignore the extension match.
				if (KMPEG4Files[i].iVideoMime == NULL)
					{
					if (videoFound)
						{
						// Try to match another extension.
						continue;
						}
					
					return KMPEG4Files[i].iAudioMime;
					}
				
				return (video ? KMPEG4Files[i].iVideoMime : KMPEG4Files[i].iAudioMime);
				}
			}
		}

	// Unknown or no extension. Try to match the brand
	while (iBrandIndex != KErrNotFound)
		{
		if (KMPEG4Files[iBrandIndex].iVideoMime == NULL)
			{
			if (videoFound)
				{
				// Try to match another brand.
				TUint32 brand = KMPEG4Files[iBrandIndex].iBrand;
				iBrandIndex = TMPEG4Parser::IsCompatibleBrand(brand, iBrandIndex + 1);
				continue;
				}
			
			return KMPEG4Files[iBrandIndex].iAudioMime;
			}
		
		return (video ? KMPEG4Files[iBrandIndex].iVideoMime : KMPEG4Files[iBrandIndex].iAudioMime);
		}

	// If there is no brand and an unknown extension look at the flags.
	// (There are some files that have no ftyp).
	// Only return a potential mime-type if all flag bits have been set.
	if (iFlags.GetBitField(KMPEG4ConfidenceMask) == KMPEG4ConfidenceMask)
		{
		return (video ? KMimeMP4_V : KMimeMP4_A);
		}
		
	return NULL;
	}


//
//
//
void TMPEG4Parser::DoRecognise(const TDesC& aFileExt, CReader& aReader, TMatch& aMatch)
	{
	TFlags flags;
	TMPEG4Parser parser(aReader, flags);
			
	TRAP_IGNORE(parser.ParseL());
	
	// The extension determines the mime-type.
	// The flags say if it's a valid MPEG4 file and if video is present.
	const TText8* extMime = parser.MatchFileType(aFileExt);
	if (extMime != NULL)
		{
		TInt confIndex = flags.GetBitField(KMPEG4ConfidenceMask);
		aMatch.iConfidence = KMPEG4FlagsToConfidence[confIndex];
		if (aMatch.iConfidence != KConfNotRecognised)
			{
			aMatch.iMime = extMime;
			}
		}
	}
	

//
//
//
void TMPEG4Parser::ParseL()
	{
	// If we have only buffer data, we must assume the video
	// content (if any) has been missed. This is because an
	// audio-only file recognised as video should play in a
	// video application, but a video file recognised as audio
	// will not play in a audio application.
	if (iReader.Type() == CReader::EBuffer)
		{
		iVideoAssumed = ETrue;
		}
		
	do
		{		
		ReadBoxHeaderL();
		if (iTitle == KFtyp)
			{
			ReadFileTypeL();
			}
		else if (iTitle == KMoov)
			{
			iFlags.SetBit(KMPEG4MOOVBit);
			ReadMovieL();
			}
		else
			{
			SkipCurrentBoxL();
			}
		}
	while (!iIsFinished);
	}


//
//
//
void TMPEG4Parser::SkipCurrentBoxL()
	{
	// Intro: [size][title] = 8 bytes.
		
	if (iSize == 0)
		{
		// The current box extends to the end of file.
		iIsFinished = ETrue;
		return;
		}
	
	iReader.SeekL(iSize - KMPEG4BoxIntroLen);
	}


//
// Parses the 'moov' box.
// This box contains sub-boxes we're interested in.
//
void TMPEG4Parser::ReadMovieL()
	{
	// This box holds no information.
	// It contains 'trak' boxes, which we want.
	
	TInt64 dataInBox = iSize - KMPEG4BoxIntroLen;
	
	while ((dataInBox > 0) && !iIsFinished)
		{
		ReadBoxHeaderL();
		dataInBox -= iSize;
		
		if (iTitle == KTrak)
			{
			iFlags.SetBit(KMPEG4TRAKBit);
			ReadTrackL();
			}
		else
			{
			SkipCurrentBoxL();
			}
		}
	}
	

//
// Parses the 'trak' box.
// This box contains sub-boxes we're interested in.
//
void TMPEG4Parser::ReadTrackL()
	{
	// This box holds no information.
	// It contains 'tkhd' boxes, which we want.
	
	TInt64 dataInBox = iSize - KMPEG4BoxIntroLen;
	
	while ((dataInBox > 0) && !iIsFinished) 
		{
		ReadBoxHeaderL();
		dataInBox -= iSize;
		
		if (iTitle == KTkhd)
			{
			ReadTrackHeaderL();
			}
		else if (iTitle == KMdia)
			{
			ReadMediaL();
			}
		else
			{
			SkipCurrentBoxL();
			}
		}
	}

//
// Parses a 'mdia' box.
// This box contains sub-boxes we're interested in.
//
void TMPEG4Parser::ReadMediaL()
	{
	TInt64 dataInBox = iSize - KMPEG4BoxIntroLen;
	
	while ((dataInBox > 0) && !iIsFinished)
		{
		ReadBoxHeaderL();
		dataInBox -= iSize;
		
		if (iTitle == KHdlr)
			{
			ReadHandlerL();
			}
		else
			{
			SkipCurrentBoxL();
			}
		}
	}


//
// Parses a 'hdlr' box.
// This is a stand-alone box.
//
void TMPEG4Parser::ReadHandlerL()
	{
	// Intro: [size][title][versionFlags][predefined][handler_type] = 20 bytes.
	const TInt KMPEG4HandlerIntroLen = 20;
	TUint32 versionFlags;
	TUint32 predefined;
	TUint32 handler;

	iReader.Read32L(versionFlags);
	iReader.Read32L(predefined);
	if (predefined != 0)
		{
		User::Leave(KErrCorrupt);
		}
		
	iReader.Read32L(handler);
	if (handler == KVide)
		{
		iFlags.SetBit(KMPEG4VideoBit);
		}
		
	iReader.SeekL(iSize - KMPEG4HandlerIntroLen);
	}


//
//
//
void TMPEG4Parser::ReadTrackHeaderL()
	{
	const TUint8 KMPEG4TrackVersion0 = 0;
	const TUint8 KMPEG4TrackVersion1 = 1;
	const TInt KMPEG4Version0ToVolume = 32; // Distance to volume field from version=0 field.
	const TInt KMPEG4Version1ToVolume = 44; // Distance to volume field from version=1 field.
	const TInt KMPEG4VolumeToWidth = 38;	// Distance to width field from volume field.

	TUint32 versionFlags;
	TUint16 volume;
	TUint32 width;
	TUint32 height;
	
	// This box contains information about a single track.
	iReader.Read32L(versionFlags);
	
	// The highest 8 bits contains the version.
	switch (HIGH_BYTE32(versionFlags))
		{
		case KMPEG4TrackVersion0:
			iReader.SeekL(KMPEG4Version0ToVolume);
			break;
			
		case KMPEG4TrackVersion1:
			iReader.SeekL(KMPEG4Version1ToVolume);
			break;
			
		default:
			User::Leave(KErrCorrupt);
		}
	
	// Volume can not be used to distinguish between audio and video anymore.
	iReader.Read16L(volume);
		
	// We want to seek ahead to read the 'width' and 'height' fields.
	iReader.SeekL(KMPEG4VolumeToWidth);

	iReader.Read32L(width);		// 16.16 fixed-point
	iReader.Read32L(height);	// 16.16 fixed-point
	if ((width != 0) && (height != 0))
		{
		iFlags.SetBit(KMPEG4VideoBit);
		}
	}

	
//
// Parses the 'ftyp' box.
// Records the first recognised brand that helps to
// identify the mime-type.
//
void TMPEG4Parser::ReadFileTypeL()
	{
	// Intro = [size][title][majorBrand] = 12 bytes.
	const TInt KMPEG4FtypIntroLen = 12;
	TUint32 brand;
	
	// If the majorBrand isn't recognised we should also
	// search the compatible brand list.
	TInt64 bytesRemaining = iSize - KMPEG4FtypIntroLen;
	//here there should be bytes remaining. Otherwise we cant read.
	if( bytesRemaining <0 )
	{
	    User::Leave(KErrCorrupt);    
	}
	
	iReader.Read32L(brand);
	iBrandIndex = TMPEG4Parser::IsCompatibleBrand(brand);
	if (iBrandIndex != KErrNotFound)
		{
		// The major brand was recognised.
		// Skip to the end of the ftyp box.
		iReader.SeekL(bytesRemaining);
		return;
		}
		
	// The major brand wasn't recognised.
	// Skip over the version info (32 bit) to the start of the
	// compatible brands list.
	TInt skip = sizeof(TUint32);
	iReader.SeekL(skip);
	bytesRemaining -= skip;
	
	while ((iBrandIndex == KErrNotFound) && (bytesRemaining > 0))
		{
		iReader.Read32L(brand);
		bytesRemaining -= skip;
		iBrandIndex = TMPEG4Parser::IsCompatibleBrand(brand);
		}

	iReader.SeekL(bytesRemaining);
	}


//
// Reads the first few bytes of a box.
// The exact amount read depends on the box.
//
void TMPEG4Parser::ReadBoxHeaderL()
	{
	const TInt KMPEG4ExtendedTypeLen = 16;
	
	TUint32 word1;

	iReader.Read32L(word1);
	iReader.Read32L(iTitle);

	switch (word1)
		{
		case 0:
			// Box extends to the end of file.
			iSize = MAKE_TINT64(0, 0);
			break;
			
		case 1:
			// Size is specified in a 64-bit field.
			iReader.Read64L(iSize);
			break;
			
		default:
			// It's an actual 32-bit size.
			iSize = MAKE_TINT64(0, word1);
		}
		
	if (iTitle == KUuid)
		{
		// Skip the extended type.
		iReader.SeekL(KMPEG4ExtendedTypeLen);
		}
	}


