[Android-port-dev] Re: [datatype-dev] CR: Extend CHXFileRecognizer for Android project
Sheldon Fu sfu at real.comI believe the correct logic should b "if (ID3 at the beginning) skip it; check MP3 header at current location;". fxd Qiang Luo wrote: > I have changed the mp3 format detection to check mp3 frame header > first per Eric's review. This has been checked in to head and > hxclient_3_1_0_atlas branch. > > Thanks Eric and Sheldon for the reviews. > > Qiang > >> Delivered-To: qluo at real.com >> Reply-To: ehyche at real.com >> From: "Eric Hyche" <ehyche at real.com> >> To: "'Qiang Luo'" <qluo at real.com>, "'Greg Wright'" <gwright at real.com>, >> "'Tony Seaward'" <tseaward at helixcommunity.org> >> Cc: "'Milko Boic'" <milko at real.com>, "'Sheldon Fu'" <sfu at real.com> >> Subject: RE: [Client-dev] CR: extend CHXFileRecognizer to cover more >> mine-types >> Date: Mon, 2 Mar 2009 10:21:55 -0500 >> Organization: RealNetworks, Inc. >> X-Mailer: Microsoft Office Outlook 12.0 >> Thread-Index: AcmZNIVEE72yww2oQ6GMMpdDPKfxoQCFU8xg >> >> Qiang, >> >> Here are my comments: >> >> +HXBOOL >> +CHXFileRecognizer::IsMP3File(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize) >> + { >> + // check for ID3 tags >> + if (0 == strncmp((const char*)pData, "ID3", 3)) >> + { >> + bResult = TRUE; >> + } >> + else >> + { >> + // check for valid frame header >> + UINT32 ulHdr; >> + UCHAR* pEnd = pData + ulSize - 4; >> + do >> + { >> + ulHdr = LoadMP3Header(pData); >> + if (IsValidMP3Header(ulHdr)) >> + { >> + bResult = TRUE; >> + break; >> + } >> + pData += 4; >> + } >> + while(pData < pEnd); >> + } >> + } >> + return bResult; >> +} >> + >> >> ID3 tags are generally at the end of .mp3 files. Also, >> ID3 tags can be found in other files, such as .aac or .amr. >> So the presence of ID3 tags is not a reliable indication >> of an .mp3 file. Your mp3 header check is the more reliable >> way. >> >> Rest looks good. >> >> ======================================= >> Eric Hyche (ehyche at real.com) >> Principal Engineer >> RealNetworks, Inc. >> >> >> >-----Original Message----- >> >From: Qiang Luo [mailto:qluo at real.com] >> Re-send to the datatype-dev group. I'm hopping to get some feedback >> for the file format detection code. >> >> Synopsis: >> Extend CHXFileRecognizer to cover additional mine-types for Android >> project. >> >> Summary: >> In Android project, we are introducing a new protocol scheme, fd://, >> claimed by the local file system, to support file descriptor as data >> source. Normally, for the local file source, the client core uses >> the file extension to load the file format plugin via >> IHXPluginHandler3. When using descriptor as data source, the content >> file name and extension will be unknown. Therefore, we need to >> extend the SDP-only CHXFileRecognizer to cover additional mine-types >> for Android project. >> >> The client core HXFileSource setup code has the following file >> mine-type discovery order: >> >> 1) look for "Content-Type" property in the request's response header. >> 2) QI the file-system's file object for IHXFileMimeMapper interface >> and call FindMimeType(). >> 3) get mime-type from url parameters. >> 4) try the file recognizer GetMimeType(m_pFileObject, >> m_pMimeFinderResponse). >> 5) try to create a file format plugin enumerator via >> IHXPluginHandler3 based on the file extension to load the plugin >> 6) fail if none of the above attempts succeeds. >> >> Adding additional mine-types detection to CHXFileRecognizer would >> alter the client core code path. To minimize the risk to other >> projects, I add #if defined() to make the addition Android platform >> only. Even for Android, we defer mime-type detection to >> FileRecognizer only when there is no file extension. >> >> Note, the detection code must be simple and small in size. It is >> intended to be just robust enough to load the correct file format >> plugin. >> >> Branch: >> head, hxclient_3_1_0_atlas >> >> File modified: >> common/system/recognizer.cpp >> common/system/pub/recognizer.h >> >> Affected platform: Android only >> >> Risks for non-android projects: low >> >> Please review the attached diff. I'm looking forward to suggestions >> to improve the format detection code. >> >> Thanks, >> >> Qiang >> >> Diffs: >> Index: recognizer.cpp >> =================================================================== >> RCS file: /cvsroot/common/system/recognizer.cpp,v >> retrieving revision 1.12 >> diff -u -r1.12 recognizer.cpp >> --- recognizer.cpp 7 Feb 2006 19:21:27 -0000 1.12 >> +++ recognizer.cpp 23 Feb 2009 17:51:01 -0000 >> @@ -4,43 +4,418 @@ >> >> #include "recognizer.h" >> >> +// file mime-types our recognizer might return >> +#define HX_MIMETYPE_RM "application/x-pn-realmedia" >> +#define HX_MIMETYPE_MP3 "audio/mp3" >> +#define HX_MIMETYPE_MP4AUDIO "audio/mp4" >> +#define HX_MIMETYPE_MP4VIDEO "video/mp4" >> +#define HX_MIMETYPE_3GPAUDIO "audio/3gpp" >> +#define HX_MIMETYPE_3GPVIDEO "video/3gpp" >> +#define HX_MIMETYPE_MPEG "video/x-mpeg" >> +#define HX_MIMETYPE_WM "application/vnd.ms-asf" >> +#define HX_MIMETYPE_AVI >> "application/x-pn-avi-plugin" >> +#define HX_MIMETYPE_OGG "application/ogg" >> +#define HX_MIMETYPE_MIDI "application/x-midi" >> +#define HX_MIMETYPE_WAVE "audio/x-wav" >> +#define HX_MIMETYPE_QT "application/x-pn-quicktime-stream" >> +#define HX_MIMETYPE_SDP "application/sdp" >> + >> +// length of buffer read from file and passed to recognizer >> +static const int KRecogLength = 512; >> + >> #if !defined (_SYMBIAN) >> HX_RESULT CHXFileRecognizer::GetMimeType(const char* pFileName, >> IHXBuffer* pBuffer, REF(IHXBuffer*) pMimeType) >> { >> - if (IsSDPFile(pBuffer)) >> + HX_RESULT retVal = HXR_FAILED; >> + char* pMimeTypeString = NULL; >> + char* pFileExtension = NULL; >> + UCHAR* pData = NULL; >> + UINT32 ulSize = 0; >> + HXBOOL bContinue = TRUE; >> + >> + // get file extension >> + if (pFileName) >> + { >> + CHXString strExtension = pFileName; >> + INT32 lPeriod = strExtension.ReverseFind('.'); >> + if (lPeriod >= 0) >> + { >> + strExtension = >> strExtension.Right(strExtension.GetLength() - lPeriod - 1); >> + if (strExtension.GetLength() > 0) >> + { >> + strExtension.MakeLower(); >> + pFileExtension = (char*)(const char*)strExtension; >> + } >> + } >> + } >> + >> + // data at the begining of the media file >> + if (pBuffer) >> + { >> + ulSize = pBuffer->GetSize(); >> + pData = pBuffer->GetBuffer(); >> + } >> + >> + // check SDP >> + if (IsSDPFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_SDP; >> + bContinue = FALSE; >> + } >> + >> + //XXXqluo to minimize the risk, the following format check is >> for ANDROID platform only >> +#if defined(ANDROID) >> + // skip format checking if the file name has an extension. >> + if (bContinue && (pFileExtension || ulSize != KRecogLength)) >> + { >> + bContinue = FALSE; >> + } >> + >> + // RealMedia >> + if (bContinue && IsRMFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_RM; >> + bContinue = FALSE; >> + } >> + >> + // 3GPP >> + if (bContinue && Is3GPPFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_3GPAUDIO; >> + bContinue = FALSE; >> + } >> + >> + // WM >> + if (bContinue && IsWMFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_WM; >> + bContinue = FALSE; >> + } >> + >> + // AVI >> + if (bContinue && IsAVIFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_AVI; >> + bContinue = FALSE; >> + } >> + >> + // OGG >> + if (bContinue && IsOGGFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_OGG; >> + bContinue = FALSE; >> + } >> + >> + // MIDI >> + if (bContinue && IsMIDIFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_MIDI; >> + bContinue = FALSE; >> + } >> + >> + // WAVE >> + if (bContinue && IsWAVEFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_WAVE; >> + bContinue = FALSE; >> + } >> + >> + // MP4 >> + if (bContinue && IsMP4File(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_MP4AUDIO; >> + bContinue = FALSE; >> + } >> + >> + // QuickTime >> + if (bContinue && IsQuickTimeFile(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_QT; >> + bContinue = FALSE; >> + } >> + >> + // MPEG >> + if (bContinue && IsMPEGFile(pData, ulSize)) >> { >> - if (HXR_OK == CreateBufferCCF(pMimeType, m_pContext)) >> + pMimeTypeString = HX_MIMETYPE_MPEG; >> + bContinue = FALSE; >> + } >> + >> + // MP3 >> + if (bContinue && IsMP3File(pData, ulSize)) >> + { >> + pMimeTypeString = HX_MIMETYPE_MP3; >> + bContinue = FALSE; >> + } >> +#endif // ANDROID >> + >> + if (pMimeTypeString) >> + { >> + retVal = CreateBufferCCF(pMimeType, m_pContext); >> + if (HXR_OK == retVal) >> { >> - int len = strlen("application/sdp"); >> - pMimeType->Set((const UCHAR*)"application/sdp", len + 1); >> + int len = strlen(pMimeTypeString); >> + pMimeType->Set((const UCHAR*)pMimeTypeString, len + 1); >> ((char*)pMimeType->GetBuffer())[len] = '\0'; >> - return HXR_OK; >> } >> } >> >> - return HXR_FAILED; >> + return retVal; >> } >> #endif >> >> HXBOOL >> -CHXFileRecognizer::IsSDPFile(IHXBuffer* pBuffer) >> +CHXFileRecognizer::IsSDPFile(UCHAR* pData, UINT32 ulSize) >> { >> HXBOOL bResult = FALSE; >> + if (pData && ulSize > 2) >> + { >> + if (0 == strncmp((const char*)pData, "v=", 2)) >> + { >> + bResult = TRUE; >> + } >> + } >> + return bResult; >> +} >> >> - if (pBuffer && pBuffer->GetSize()) >> +HXBOOL >> +CHXFileRecognizer::IsRMFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + UCHAR pRAMagic[4] = {'.', 'r', 'a', 0xFD}; >> + if (pData && ulSize > 4) >> { >> - if (0 == strncmp((const char*)pBuffer->GetBuffer(), "v=", 2)) >> + if (0 == strncmp((const char*)pData, ".RMF", 4) >> || // .RMF >> + 0 == strncmp((const char*)pData, ".RMS", 4) >> || // .RMS >> + 0 == memcmp(pData, pRAMagic, >> 4)) // .ra\0xFD >> { >> bResult = TRUE; >> } >> } >> + return bResult; >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsQuickTimeFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize > 8) >> + { >> + // check atom types >> + pData += 4; >> + if (0 == strncmp((const char*)pData, "moov", 4) || >> + 0 == strncmp((const char*)pData, "free", 4) || >> + 0 == strncmp((const char*)pData, "skip", 4) || >> + 0 == strncmp((const char*)pData, "mdat", 4) || >> + 0 == strncmp((const char*)pData, "wide", 4) || >> + 0 == strncmp((const char*)pData, "pnot", 4)) >> + { >> + //XXXqluo todo: validate atom. >> + bResult = TRUE; >> + } >> + } >> + return bResult; >> +} >> >> +HXBOOL >> +CHXFileRecognizer::IsWMFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize > 16) >> + { >> + // WM header magic >> + UCHAR pMagic[16] = {0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, >> 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}; >> + if (memcmp(pData, pMagic, 16) == 0) >> + { >> + bResult = TRUE; >> + } >> + } >> return bResult; >> } >> >> -// length of buffer read from file and passed to recognizer >> -static const int KRecogLength = 512; >> +HXBOOL >> +CHXFileRecognizer::IsAVIFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize > 16) >> + { >> + UCHAR pMagic1[4] = {'R', 'I', 'F', >> 'F'}; // RIFF >> + UCHAR pMagic2[8] = {'A', 'V', 'I', ' ', 'L', 'I', 'S', >> 'T'}; // AVI LIST chunk >> + if (memcmp(pData, pMagic1, 4) == 0 || >> + memcmp(pData+8, pMagic2, 8) == 0) >> + { >> + bResult = TRUE; >> + } >> + } >> + return bResult; >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsOGGFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize > 4) >> + { >> + UCHAR pMagic[4] = {'O', 'g', 'g', >> 'S'}; // page magic "OggS" >> + if (memcmp(pData, pMagic, 4) == 0) >> + { >> + bResult = TRUE; >> + } >> + } >> + return bResult; >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsMIDIFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize > 4) >> + { >> + UCHAR pMagic[4] = {'M', 'T', 'h', >> 'd'}; // MIDI header chunk ID >> + if (memcmp(pData, pMagic, 4) == 0) >> + { >> + bResult = TRUE; >> + } >> + } >> + return bResult; >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsWAVEFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize > 12) >> + { >> + UCHAR pMagic1[4] = {'R', 'I', 'F', >> 'F'}; // Chunk ID >> + UCHAR pMagic2[4] = {'W', 'A', 'V', >> 'E'}; // Format >> + if (memcmp(pData, pMagic1, 4) == 0 || >> + memcmp(pData+8, pMagic2, 4) == 0) >> + { >> + bResult = TRUE; >> + } >> + } >> + return bResult; >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsMPEGFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize > 4) >> + { >> + UCHAR pMagic[4] = {0x00, 0x00, 0x01, >> 0xb3}; // MPEG sequence header start code >> + if (memcmp(pData, pMagic, 4) == 0) >> + { >> + bResult = TRUE; >> + } >> + } >> + return bResult; >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsMP3File(UCHAR* pData, UINT32 ulSize) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize) >> + { >> + // check for ID3 tags >> + if (0 == strncmp((const char*)pData, "ID3", 3)) >> + { >> + bResult = TRUE; >> + } >> + else >> + { >> + // check for valid frame header >> + UINT32 ulHdr; >> + UCHAR* pEnd = pData + ulSize - 4; >> + do >> + { >> + ulHdr = LoadMP3Header(pData); >> + if (IsValidMP3Header(ulHdr)) >> + { >> + bResult = TRUE; >> + break; >> + } >> + pData += 4; >> + } >> + while(pData < pEnd); >> + } >> + } >> + return bResult; >> +} >> + >> +UINT32 >> +CHXFileRecognizer::LoadMP3Header(UCHAR* p) >> +{ >> + return (UINT32)(((p[0] & 255) << 24) | ((p[1] & 255) << 16) | >> ((p[2] & 255) << 8) | ((p[3] & 255))); >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsValidMP3Header(UINT32 ulHdr) >> +{ >> + return (((getFrameSync(ulHdr) & 2047)==2047) && >> + ((getVersionIndex(ulHdr) & 3)!= 1) && >> + ((getLayerIndex(ulHdr) & 3)!= 0) && >> + ((getBitrateIndex(ulHdr) & 15)!= 0) && >> + ((getBitrateIndex(ulHdr) & 15)!= 15) && >> + ((getFrequencyIndex(ulHdr) & 3)!= 3) && >> + ((getEmphasisIndex(ulHdr) & 3)!= 2) ); >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::Is3GPPFile(UCHAR* pData, UINT32 ulSize) >> +{ >> + return IsISOMediaFile(pData, ulSize, "3gp"); >> +} >> >> +HXBOOL >> +CHXFileRecognizer::IsMP4File(UCHAR* pData, UINT32 ulSize) >> +{ >> + if (IsISOMediaFile(pData, ulSize, "M4A") || >> + IsISOMediaFile(pData, ulSize, "mp4")) >> + { >> + return TRUE; >> + } >> + else >> + { >> + return FALSE; >> + } >> +} >> + >> +HXBOOL >> +CHXFileRecognizer::IsISOMediaFile(UCHAR* pData, UINT32 ulSize, const >> char* pBrand) >> +{ >> + HXBOOL bResult = FALSE; >> + if (pData && ulSize) >> + { >> + UCHAR* pEnd = (pData + ulSize - 4); >> + >> + // locate file type box >> + pData += 4; >> + HXBOOL bFound = FALSE; >> + while (pData < pEnd) >> + { >> + // scan for "ftyp" >> + if ((*pData == 'f' && *(pData+1) == 't' && *(pData+2) == >> 'y' && *(pData+3) == 'p')) >> + { >> + bFound = TRUE; >> + break; >> + } >> + pData++; >> + } >> + >> + // match for the given type(pBrand) >> + if (bFound) >> + { >> + pData += 4; >> + if (0 == strncmp((const char*)pData, pBrand, >> strlen(pBrand))) >> + { >> + bResult = TRUE; >> + } >> + } >> + } >> + return bResult; >> +} >> >> CHXFileRecognizer::CHXFileRecognizer(IUnknown* pContext) >> : m_lRefCount(0), >> Index: pub/recognizer.h >> =================================================================== >> RCS file: /cvsroot/common/system/pub/recognizer.h,v >> retrieving revision 1.5 >> diff -u -r1.5 recognizer.h >> --- pub/recognizer.h 7 Feb 2006 19:21:27 -0000 1.5 >> +++ pub/recognizer.h 23 Feb 2009 17:51:02 -0000 >> @@ -51,9 +51,30 @@ >> IHXBuffer* /* IN */ pBuffer, >> REF(IHXBuffer*) /* OUT*/ pMimeType); >> >> - HXBOOL IsSDPFile(IHXBuffer* pBuffer); >> + HXBOOL IsSDPFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsRMFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsMP3File(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsISOMediaFile(UCHAR* pData, UINT32 ulSize, const char* >> pBrand); >> + HXBOOL IsMP4File(UCHAR* pData, UINT32 ulSize); >> + HXBOOL Is3GPPFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsWMFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsAVIFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsOGGFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsMIDIFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsWAVEFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsMPEGFile(UCHAR* pData, UINT32 ulSize); >> + HXBOOL IsQuickTimeFile(UCHAR* pData, UINT32 ulSize); >> void DoFileRecognize(void); >> >> + UINT32 LoadMP3Header(UCHAR* pData); >> + HXBOOL IsValidMP3Header(UINT32 ulHdr); >> + INT32 getFrameSync(UINT32 ulHdr) { return >> (int)((ulHdr>>21) & 2047); }; >> + INT32 getVersionIndex(UINT32 ulHdr) { return >> (int)((ulHdr>>19) & 3); }; >> + INT32 getLayerIndex(UINT32 ulHdr) { return >> (int)((ulHdr>>17) & 3); }; >> + INT32 getBitrateIndex(UINT32 ulHdr) { return >> (int)((ulHdr>>12) & 15); }; >> + INT32 getFrequencyIndex(UINT32 ulHdr) { return >> (int)((ulHdr>>10) & 3); }; >> + INT32 getEmphasisIndex(UINT32 ulHdr) { return (int)(ulHdr & >> 3); }; >> + >> private: >> LONG32 m_lRefCount; >> IUnknown* m_pContext; >> >> >> _______________________________________________ >> Datatype-dev mailing list >> Datatype-dev at helixcommunity.org >> http://lists.helixcommunity.org/mailman/listinfo/datatype-dev > > > > _______________________________________________ > Android-port-dev mailing list > Android-port-dev at lists.helixcommunity.org > http://lists.helixcommunity.org/mailman/listinfo/android-port-dev >