mmlibs/mmfw/Recogniser/src/mpeg4parser.cpp
changeset 0 40261b775718
child 11 d5f04de580b7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mmlibs/mmfw/Recogniser/src/mpeg4parser.cpp	Tue Feb 02 01:56:55 2010 +0200
@@ -0,0 +1,534 @@
+// 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.
+
+//
+// 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}
+	};
+
+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;
+	
+	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);
+		}
+	}
+
+