sl@0: // Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies). sl@0: // All rights reserved. sl@0: // This component and the accompanying materials are made available sl@0: // under the terms of "Eclipse Public License v1.0" sl@0: // which accompanies this distribution, and is available sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: // sl@0: // Initial Contributors: sl@0: // Nokia Corporation - initial contribution. sl@0: // sl@0: // Contributors: sl@0: // sl@0: // Description: sl@0: // sl@0: sl@0: #include "parsers.h" sl@0: #include "constants.h" sl@0: sl@0: // sl@0: // Brands. sl@0: // Some (most) brands have Release information - e.g. 3gp6, 3gp5 etc. sl@0: // Therefore, to match the brand, we only look at the characters that don't sl@0: // represent Release information (in the above case, the 4th character). sl@0: // The Release character is set to zero. sl@0: // sl@0: static const TUint32 KMP4Brand = MAKE_INT32('m', 'p', '4', 0); sl@0: static const TUint32 K3GPBrand = MAKE_INT32('3', 'g', 'p', 0); sl@0: static const TUint32 K3G2Brand = MAKE_INT32('3', 'g', '2', 0); sl@0: static const TUint32 K3GSBrand = MAKE_INT32('3', 'g', 's', 0); // Streaming servers. sl@0: static const TUint32 K3GRBrand = MAKE_INT32('3', 'g', 'r', 0); // Progresive download and MMS. sl@0: static const TUint32 KQTBrand = MAKE_INT32('q', 't', ' ', ' '); // for quicktime sl@0: // sl@0: // Box identifiers. sl@0: // sl@0: static const TUint32 KFtyp = MAKE_INT32('f', 't', 'y', 'p'); sl@0: static const TUint32 KMoov = MAKE_INT32('m', 'o', 'o', 'v'); sl@0: static const TUint32 KTrak = MAKE_INT32('t', 'r', 'a', 'k'); sl@0: static const TUint32 KTkhd = MAKE_INT32('t', 'k', 'h', 'd'); sl@0: static const TUint32 KMdia = MAKE_INT32('m', 'd', 'i', 'a'); sl@0: static const TUint32 KHdlr = MAKE_INT32('h', 'd', 'l', 'r'); sl@0: static const TUint32 KVide = MAKE_INT32('v', 'i', 'd', 'e'); sl@0: static const TUint32 KUuid = MAKE_INT32('u', 'u', 'i', 'd'); sl@0: sl@0: sl@0: // sl@0: // This truth table maps the following flags to a confidence level. sl@0: // sl@0: // A - trak box present sl@0: // B - moov box present sl@0: // sl@0: // A B -> Confidence sl@0: // ================= sl@0: // 0 0 -> EPossible sl@0: // 0 1 -> EProbable sl@0: // 1 0 -> EProbable sl@0: // 1 1 -> ECertain sl@0: // sl@0: static const TInt KMPEG4FlagsToConfidence[] = sl@0: { sl@0: KConfPossible, sl@0: KConfProbable, sl@0: KConfProbable, sl@0: KConfCertain, sl@0: }; sl@0: sl@0: sl@0: #define KMPEG4ConfidenceMask 0x03 // 00000011 sl@0: #define KMPEG4BoxTitleLen 4 sl@0: #define KMPEG4TRAKBit KBit0 // 00000001 sl@0: #define KMPEG4MOOVBit KBit1 // 00000010 sl@0: #define KMPEG4VideoBit KBit2 // 00000100 sl@0: sl@0: static const TInt KMPEG4BoxIntroLen = 8; sl@0: static const TInt KMPEG4Box64BitIntroLen = 16; sl@0: sl@0: // sl@0: // In order to find out the type of MPEG4 file it is sl@0: // we need to be able to map known extensions, expected sl@0: // ftyp expressions with MIME-types. sl@0: // sl@0: typedef struct sl@0: { sl@0: const TText* iExt; sl@0: TUint32 iBrand; sl@0: const TText8* iAudioMime; sl@0: const TText8* iVideoMime; sl@0: } sl@0: TMPEG4File; sl@0: sl@0: sl@0: // sl@0: // A table of ftyp's, extensions and mime-types. sl@0: // sl@0: // .mp4 - can contain either audio or video. sl@0: // .m4a - should contain (unprotected) audio only. sl@0: // .m4p - should contain protected audio only. sl@0: // .3gp - can contain either audio or video. sl@0: // sl@0: static const TMPEG4File KMPEG4Files[] = sl@0: { sl@0: {KExtMP4, KMP4Brand, KMimeMP4_A, KMimeMP4_V}, sl@0: {KExt3GP, K3GPBrand, KMime3GP_A, KMime3GP_V}, sl@0: {KExtM4A, KMP4Brand, KMimeMP4_A, NULL}, sl@0: {KExt3G2, K3G2Brand, KMime3G2_A, KMime3G2_V}, sl@0: {KExt3GP, K3GSBrand, KMime3GP_A, KMime3GP_V}, sl@0: {KExt3GP, K3GRBrand, KMime3GP_A, KMime3GP_V}, sl@0: {KExt3GA, K3GPBrand, KMime3GA, NULL}, sl@0: {KExtMOV, KQTBrand, NULL, KMimeQuickV} // this entry is for .mov files sl@0: }; sl@0: sl@0: static const TInt KMPEG4FileTypeCount = sizeof(KMPEG4Files) / sizeof(TMPEG4File); sl@0: sl@0: sl@0: // sl@0: // sl@0: // sl@0: TMPEG4Parser::TMPEG4Parser(CReader& aReader, TFlags& aFlags) sl@0: : iBrandIndex(KErrNotFound), sl@0: iIsFinished(EFalse), sl@0: iReader(aReader), sl@0: iFlags(aFlags), sl@0: iVideoAssumed(EFalse) sl@0: { sl@0: } sl@0: sl@0: sl@0: // sl@0: // Compare a brand with the ones this recogniser knows about. sl@0: // sl@0: TInt TMPEG4Parser::IsCompatibleBrand(TUint32 aBrand, TInt aStartPos) sl@0: { sl@0: for (TInt i = aStartPos; i < KMPEG4FileTypeCount; i++) sl@0: { sl@0: TUint32 knownBrand = KMPEG4Files[i].iBrand; sl@0: if ((aBrand & knownBrand) == knownBrand) sl@0: { sl@0: return i; sl@0: } sl@0: } sl@0: sl@0: return KErrNotFound; sl@0: } sl@0: sl@0: sl@0: // sl@0: // Try to determine the mime-type from the extension, and sl@0: // if that isn't matched, from the FTYP field if present. sl@0: // If none of these are matched, NULL is returned and the sl@0: // file isn't a valid MPEG4 file. sl@0: // sl@0: const TText8* TMPEG4Parser::MatchFileType(const TDesC& aExt) sl@0: { sl@0: TBool videoFound = iFlags.GetBitField(KMPEG4VideoBit); sl@0: TBool video = (videoFound || iVideoAssumed); sl@0: sl@0: // Try to match the extension. sl@0: if (aExt.Length() > 0) sl@0: { sl@0: for (TInt i = 0; i < KMPEG4FileTypeCount; i++) sl@0: { sl@0: TPtrC ext(KMPEG4Files[i].iExt); sl@0: if (aExt.MatchF(ext) != KErrNotFound) sl@0: { sl@0: // Extension match. If the extension is for an audio-only format sl@0: // we must make sure there is no video content in the file. If sl@0: // video content is present then ignore the extension match. sl@0: if (KMPEG4Files[i].iVideoMime == NULL) sl@0: { sl@0: if (videoFound) sl@0: { sl@0: // Try to match another extension. sl@0: continue; sl@0: } sl@0: sl@0: return KMPEG4Files[i].iAudioMime; sl@0: } sl@0: sl@0: return (video ? KMPEG4Files[i].iVideoMime : KMPEG4Files[i].iAudioMime); sl@0: } sl@0: } sl@0: } sl@0: sl@0: // Unknown or no extension. Try to match the brand sl@0: while (iBrandIndex != KErrNotFound) sl@0: { sl@0: if (KMPEG4Files[iBrandIndex].iVideoMime == NULL) sl@0: { sl@0: if (videoFound) sl@0: { sl@0: // Try to match another brand. sl@0: TUint32 brand = KMPEG4Files[iBrandIndex].iBrand; sl@0: iBrandIndex = TMPEG4Parser::IsCompatibleBrand(brand, iBrandIndex + 1); sl@0: continue; sl@0: } sl@0: sl@0: return KMPEG4Files[iBrandIndex].iAudioMime; sl@0: } sl@0: sl@0: return (video ? KMPEG4Files[iBrandIndex].iVideoMime : KMPEG4Files[iBrandIndex].iAudioMime); sl@0: } sl@0: sl@0: // If there is no brand and an unknown extension look at the flags. sl@0: // (There are some files that have no ftyp). sl@0: // Only return a potential mime-type if all flag bits have been set. sl@0: if (iFlags.GetBitField(KMPEG4ConfidenceMask) == KMPEG4ConfidenceMask) sl@0: { sl@0: return (video ? KMimeMP4_V : KMimeMP4_A); sl@0: } sl@0: sl@0: return NULL; sl@0: } sl@0: sl@0: sl@0: // sl@0: // sl@0: // sl@0: void TMPEG4Parser::DoRecognise(const TDesC& aFileExt, CReader& aReader, TMatch& aMatch) sl@0: { sl@0: TFlags flags; sl@0: TMPEG4Parser parser(aReader, flags); sl@0: sl@0: TRAP_IGNORE(parser.ParseL()); sl@0: sl@0: // The extension determines the mime-type. sl@0: // The flags say if it's a valid MPEG4 file and if video is present. sl@0: const TText8* extMime = parser.MatchFileType(aFileExt); sl@0: if (extMime != NULL) sl@0: { sl@0: TInt confIndex = flags.GetBitField(KMPEG4ConfidenceMask); sl@0: aMatch.iConfidence = KMPEG4FlagsToConfidence[confIndex]; sl@0: if (aMatch.iConfidence != KConfNotRecognised) sl@0: { sl@0: aMatch.iMime = extMime; sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: // sl@0: // sl@0: // sl@0: void TMPEG4Parser::ParseL() sl@0: { sl@0: // If we have only buffer data, we must assume the video sl@0: // content (if any) has been missed. This is because an sl@0: // audio-only file recognised as video should play in a sl@0: // video application, but a video file recognised as audio sl@0: // will not play in a audio application. sl@0: if (iReader.Type() == CReader::EBuffer) sl@0: { sl@0: iVideoAssumed = ETrue; sl@0: } sl@0: sl@0: do sl@0: { sl@0: ReadBoxHeaderL(); sl@0: if (iTitle == KFtyp) sl@0: { sl@0: ReadFileTypeL(); sl@0: } sl@0: else if (iTitle == KMoov) sl@0: { sl@0: iFlags.SetBit(KMPEG4MOOVBit); sl@0: ReadMovieL(); sl@0: } sl@0: else sl@0: { sl@0: SkipCurrentBoxL(); sl@0: } sl@0: } sl@0: while (!iIsFinished); sl@0: } sl@0: sl@0: sl@0: // sl@0: // sl@0: // sl@0: void TMPEG4Parser::SkipCurrentBoxL() sl@0: { sl@0: // Intro: [size][title] = 8 bytes. sl@0: sl@0: if (iSize == 0) sl@0: { sl@0: // The current box extends to the end of file. sl@0: iIsFinished = ETrue; sl@0: return; sl@0: } sl@0: if(iSizeIn32bit) sl@0: { sl@0: iReader.SeekL(iSize - KMPEG4BoxIntroLen); sl@0: } sl@0: else sl@0: { sl@0: iReader.SeekL(iSize - KMPEG4Box64BitIntroLen); sl@0: } sl@0: } sl@0: sl@0: sl@0: // sl@0: // Parses the 'moov' box. sl@0: // This box contains sub-boxes we're interested in. sl@0: // sl@0: void TMPEG4Parser::ReadMovieL() sl@0: { sl@0: // This box holds no information. sl@0: // It contains 'trak' boxes, which we want. sl@0: sl@0: TInt64 dataInBox = iSize - KMPEG4BoxIntroLen; sl@0: sl@0: while ((dataInBox > 0) && !iIsFinished) sl@0: { sl@0: ReadBoxHeaderL(); sl@0: dataInBox -= iSize; sl@0: sl@0: if (iTitle == KTrak) sl@0: { sl@0: iFlags.SetBit(KMPEG4TRAKBit); sl@0: ReadTrackL(); sl@0: } sl@0: else sl@0: { sl@0: SkipCurrentBoxL(); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: // sl@0: // Parses the 'trak' box. sl@0: // This box contains sub-boxes we're interested in. sl@0: // sl@0: void TMPEG4Parser::ReadTrackL() sl@0: { sl@0: // This box holds no information. sl@0: // It contains 'tkhd' boxes, which we want. sl@0: sl@0: TInt64 dataInBox = iSize - KMPEG4BoxIntroLen; sl@0: sl@0: while ((dataInBox > 0) && !iIsFinished) sl@0: { sl@0: ReadBoxHeaderL(); sl@0: dataInBox -= iSize; sl@0: sl@0: if (iTitle == KTkhd) sl@0: { sl@0: ReadTrackHeaderL(); sl@0: } sl@0: else if (iTitle == KMdia) sl@0: { sl@0: ReadMediaL(); sl@0: } sl@0: else sl@0: { sl@0: SkipCurrentBoxL(); sl@0: } sl@0: } sl@0: } sl@0: sl@0: // sl@0: // Parses a 'mdia' box. sl@0: // This box contains sub-boxes we're interested in. sl@0: // sl@0: void TMPEG4Parser::ReadMediaL() sl@0: { sl@0: TInt64 dataInBox = iSize - KMPEG4BoxIntroLen; sl@0: sl@0: while ((dataInBox > 0) && !iIsFinished) sl@0: { sl@0: ReadBoxHeaderL(); sl@0: dataInBox -= iSize; sl@0: sl@0: if (iTitle == KHdlr) sl@0: { sl@0: ReadHandlerL(); sl@0: } sl@0: else sl@0: { sl@0: SkipCurrentBoxL(); sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: // sl@0: // Parses a 'hdlr' box. sl@0: // This is a stand-alone box. sl@0: // sl@0: void TMPEG4Parser::ReadHandlerL() sl@0: { sl@0: // Intro: [size][title][versionFlags][predefined][handler_type] = 20 bytes. sl@0: const TInt KMPEG4HandlerIntroLen = 20; sl@0: TUint32 versionFlags; sl@0: TUint32 predefined; sl@0: TUint32 handler; sl@0: sl@0: iReader.Read32L(versionFlags); sl@0: iReader.Read32L(predefined); sl@0: if (predefined != 0) sl@0: { sl@0: User::Leave(KErrCorrupt); sl@0: } sl@0: sl@0: iReader.Read32L(handler); sl@0: if (handler == KVide) sl@0: { sl@0: iFlags.SetBit(KMPEG4VideoBit); sl@0: } sl@0: sl@0: iReader.SeekL(iSize - KMPEG4HandlerIntroLen); sl@0: } sl@0: sl@0: sl@0: // sl@0: // sl@0: // sl@0: void TMPEG4Parser::ReadTrackHeaderL() sl@0: { sl@0: const TUint8 KMPEG4TrackVersion0 = 0; sl@0: const TUint8 KMPEG4TrackVersion1 = 1; sl@0: const TInt KMPEG4Version0ToVolume = 32; // Distance to volume field from version=0 field. sl@0: const TInt KMPEG4Version1ToVolume = 44; // Distance to volume field from version=1 field. sl@0: const TInt KMPEG4VolumeToWidth = 38; // Distance to width field from volume field. sl@0: sl@0: TUint32 versionFlags; sl@0: TUint16 volume; sl@0: TUint32 width; sl@0: TUint32 height; sl@0: sl@0: // This box contains information about a single track. sl@0: iReader.Read32L(versionFlags); sl@0: sl@0: // The highest 8 bits contains the version. sl@0: switch (HIGH_BYTE32(versionFlags)) sl@0: { sl@0: case KMPEG4TrackVersion0: sl@0: iReader.SeekL(KMPEG4Version0ToVolume); sl@0: break; sl@0: sl@0: case KMPEG4TrackVersion1: sl@0: iReader.SeekL(KMPEG4Version1ToVolume); sl@0: break; sl@0: sl@0: default: sl@0: User::Leave(KErrCorrupt); sl@0: } sl@0: sl@0: // Volume can not be used to distinguish between audio and video anymore. sl@0: iReader.Read16L(volume); sl@0: sl@0: // We want to seek ahead to read the 'width' and 'height' fields. sl@0: iReader.SeekL(KMPEG4VolumeToWidth); sl@0: sl@0: iReader.Read32L(width); // 16.16 fixed-point sl@0: iReader.Read32L(height); // 16.16 fixed-point sl@0: if ((width != 0) && (height != 0)) sl@0: { sl@0: iFlags.SetBit(KMPEG4VideoBit); sl@0: } sl@0: } sl@0: sl@0: sl@0: // sl@0: // Parses the 'ftyp' box. sl@0: // Records the first recognised brand that helps to sl@0: // identify the mime-type. sl@0: // sl@0: void TMPEG4Parser::ReadFileTypeL() sl@0: { sl@0: // Intro = [size][title][majorBrand] = 12 bytes. sl@0: const TInt KMPEG4FtypIntroLen = 12; sl@0: TUint32 brand; sl@0: sl@0: // If the majorBrand isn't recognised we should also sl@0: // search the compatible brand list. sl@0: TInt64 bytesRemaining = iSize - KMPEG4FtypIntroLen; sl@0: //here there should be bytes remaining. Otherwise we cant read. sl@0: if( bytesRemaining <0 ) sl@0: { sl@0: User::Leave(KErrCorrupt); sl@0: } sl@0: sl@0: iReader.Read32L(brand); sl@0: iBrandIndex = TMPEG4Parser::IsCompatibleBrand(brand); sl@0: if (iBrandIndex != KErrNotFound) sl@0: { sl@0: // The major brand was recognised. sl@0: // Skip to the end of the ftyp box. sl@0: iReader.SeekL(bytesRemaining); sl@0: return; sl@0: } sl@0: sl@0: // The major brand wasn't recognised. sl@0: // Skip over the version info (32 bit) to the start of the sl@0: // compatible brands list. sl@0: TInt skip = sizeof(TUint32); sl@0: iReader.SeekL(skip); sl@0: bytesRemaining -= skip; sl@0: sl@0: while ((iBrandIndex == KErrNotFound) && (bytesRemaining > 0)) sl@0: { sl@0: iReader.Read32L(brand); sl@0: bytesRemaining -= skip; sl@0: iBrandIndex = TMPEG4Parser::IsCompatibleBrand(brand); sl@0: } sl@0: sl@0: iReader.SeekL(bytesRemaining); sl@0: } sl@0: sl@0: sl@0: // sl@0: // Reads the first few bytes of a box. sl@0: // The exact amount read depends on the box. sl@0: // sl@0: void TMPEG4Parser::ReadBoxHeaderL() sl@0: { sl@0: const TInt KMPEG4ExtendedTypeLen = 16; sl@0: sl@0: TUint32 word1; sl@0: sl@0: iReader.Read32L(word1); sl@0: iReader.Read32L(iTitle); sl@0: sl@0: switch (word1) sl@0: { sl@0: case 0: sl@0: // Box extends to the end of file. sl@0: iSize = MAKE_TINT64(0, 0); sl@0: break; sl@0: sl@0: case 1: sl@0: // Size is specified in a 64-bit field. sl@0: iSizeIn32bit = EFalse; sl@0: iReader.Read64L(iSize); sl@0: break; sl@0: sl@0: default: sl@0: // It's an actual 32-bit size. sl@0: iSizeIn32bit = ETrue; sl@0: iSize = MAKE_TINT64(0, word1); sl@0: } sl@0: sl@0: if (iTitle == KUuid) sl@0: { sl@0: // Skip the extended type. sl@0: iReader.SeekL(KMPEG4ExtendedTypeLen); sl@0: } sl@0: } sl@0: sl@0: