Android开发之媒体扫描详细解析(下)

来源:互联网 发布:php开发好学吗 编辑:程序博客网 时间:2024/05/15 12:43

上篇blog说到了经过对文件夹进行扫描如果后缀符合系统设定的一些格式,那么就会进行文件内容扫描下面我们紧接着STEP 14中的

[cpp] view plain copy
  1. status_t StagefrightMediaScanner::processFile(  
  2.         const char *path, const char *mimeType,  
  3.         MediaScannerClient &client) {  
  4.     LOGV(”processFile ’%s’.”, path);  
  5.   
  6.   
  7.     client.setLocale(locale());  
  8.     client.beginFile();  
  9.   
  10.   
  11.     const char *extension = strrchr(path, ‘.’);  
  12.   
  13.   
  14.     if (!extension) {  
  15.         return UNKNOWN_ERROR;  
  16.     }  
  17.   
  18.   
  19.     if (!FileHasAcceptableExtension(extension)) {  
  20.         client.endFile();  
  21.   
  22.   
  23.         return UNKNOWN_ERROR;  
  24.     }  
  25.   
  26.   
  27.     if (!strcasecmp(extension, “.mid”)  
  28.             || !strcasecmp(extension, ”.smf”)  
  29.             || !strcasecmp(extension, ”.imy”)  
  30.             || !strcasecmp(extension, ”.midi”)  
  31.             || !strcasecmp(extension, ”.xmf”)  
  32.             || !strcasecmp(extension, ”.rtttl”)  
  33.             || !strcasecmp(extension, ”.rtx”)  
  34.             || !strcasecmp(extension, ”.ota”)) {  
  35.         return HandleMIDI(path, &client);  
  36.     }  
  37.   
  38.   
  39.     if (mRetriever->setDataSource(path) == OK) {  
  40.         const char *value;  
  41.         if ((value = mRetriever->extractMetadata(  
  42.                         METADATA_KEY_MIMETYPE)) != NULL) {  
  43.             client.setMimeType(value);  
  44.         }  
  45.   
  46.   
  47.         struct KeyMap {  
  48.             const char *tag;  
  49.             int key;  
  50.         };  
  51.         static const KeyMap kKeyMap[] = {  
  52.             { ”tracknumber”, METADATA_KEY_CD_TRACK_NUMBER },  
  53.             { ”discnumber”, METADATA_KEY_DISC_NUMBER },  
  54.             { ”album”, METADATA_KEY_ALBUM },  
  55.             { ”artist”, METADATA_KEY_ARTIST },  
  56.             { ”albumartist”, METADATA_KEY_ALBUMARTIST },  
  57.             { ”composer”, METADATA_KEY_COMPOSER },  
  58.             { ”genre”, METADATA_KEY_GENRE },  
  59.             { ”title”, METADATA_KEY_TITLE },  
  60.             { ”year”, METADATA_KEY_YEAR },  
  61.             { ”duration”, METADATA_KEY_DURATION },  
  62.             { ”writer”, METADATA_KEY_WRITER },  
  63.             { ”compilation”, METADATA_KEY_COMPILATION },  
  64.         };  
  65.         static const size_t kNumEntries = sizeof(kKeyMap) / sizeof(kKeyMap[0]);  
  66.   
  67.   
  68.         for (size_t i = 0; i < kNumEntries; ++i) {  
  69.             const char *value;  
  70.             if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) != NULL) {  
  71.                 client.addStringTag(kKeyMap[i].tag, value);  
  72.             }  
  73.         }  
  74.     }  
  75.   
  76.   
  77.     client.endFile();  
  78.   
  79.   
  80.     return OK;  
  81. }  


来进行代码跟进说明,首先StagefrightMediaScanner是Stagefright的一部分,它负责媒体扫描工作,而stagefright是整个android系统media处理的框架,包括音视频的播放。

mRetriever->setDataSource(path),mRetriever是在StagefrightMediaScanner的构造函数中创建的

[cpp] view plain copy
  1. StagefrightMediaScanner::StagefrightMediaScanner()  
  2.     : mRetriever(new MediaMetadataRetriever) {  
  3. }  


STEP15
[cpp] view plain copy
  1. status_t MediaMetadataRetriever::setDataSource(const char* srcUrl)  
  2. {  
  3.     LOGV(”setDataSource”);  
  4.     Mutex::Autolock _l(mLock);  
  5.     if (mRetriever == 0) {  
  6.         LOGE(”retriever is not initialized”);  
  7.         return INVALID_OPERATION;  
  8.     }  
  9.     if (srcUrl == NULL) {  
  10.         LOGE(”data source is a null pointer”);  
  11.         return UNKNOWN_ERROR;  
  12.     }  
  13.     LOGV(”data source (%s)”, srcUrl);  
  14.     return mRetriever->setDataSource(srcUrl);  
  15. }  



再看下MediaMetadataRetriever里面的mRetriever也是在MediaMetadataRetriever的构造函数中创建的。并且是通过MediaPlayerService来创建,实际就是创建的StagefrightMetadataRetriever对象。紧接着看StagefrightMetadataRetriever的setDataSource函数

STEP15

[cpp] view plain copy
  1. status_t StagefrightMetadataRetriever::setDataSource(const char *uri) {  
  2.     LOGV(”setDataSource(%s)”, uri);  
  3.   
  4.     mParsedMetaData = false;  
  5.     mMetaData.clear();  
  6.     delete mAlbumArt;  
  7.     mAlbumArt = NULL;  
  8.   
  9.     mSource = DataSource::CreateFromURI(uri);  
  10.   
  11.     if (mSource == NULL) {  
  12.         return UNKNOWN_ERROR;  
  13.     }  
  14.   
  15.     mExtractor = MediaExtractor::Create(mSource);  
  16.   
  17.     if (mExtractor == NULL) {  
  18.         mSource.clear();  
  19.   
  20.         return UNKNOWN_ERROR;  
  21.     }  
  22.   
  23.     return OK;  
  24. }  



有阅读过stagefright源码的同学可能看到这个地方就会感觉很熟悉,首先根据URI创建了一个DataSource  DataSource::CreateFromURI(uri);DataSource我们实际可以将它理解为文件的源,它里面会先把文件打开,然后对文件描述符进行读取操作。

看源码可以知道它会创建一个FileSource。然后根据这个DataSource创建一个MediaExtractor::Create(mSource);  MediaExtractor就是文件解析器

STEP16

[cpp] view plain copy
  1. sp<MediaExtractor> MediaExtractor::Create(  
  2.         const sp<DataSource> &source, const char *mime) {  
  3.     sp<AMessage> meta;  
  4.   
  5.     String8 tmp;  
  6.     if (mime == NULL) {  
  7.         float confidence;  
  8.         if (!source->sniff(&tmp, &confidence, &meta)) {  
  9.             LOGV(”FAILED to autodetect media content.”);  
  10.   
  11.             return NULL;  
  12.         }  
  13.   
  14.         mime = tmp.string();  
  15.         LOGV(”Autodetected media content as ’%s’ with confidence %.2f”,  
  16.              mime, confidence);  
  17.     }  
  18.   
  19.     if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)  
  20.             || !strcasecmp(mime, ”audio/mp4”)) {  
  21.         return new MPEG4Extractor(source);  
  22.     }   
  23. …  
  24. }  
  25.  …  
  26. }  


这里面有一个很关键的函数source->sniff(&tmp, &confidence, &meta),字面上看就是嗅探的意思,非常形象,DataSource里面有一个sniff函数

STEP 17

[cpp] view plain copy
  1. bool DataSource::sniff(  
  2.         String8 *mimeType, float *confidence, sp<AMessage> *meta) {  
  3.     *mimeType = ”“;  
  4.     *confidence = 0.0f;  
  5.     meta->clear();  
  6.   
  7.     Mutex::Autolock autoLock(gSnifferMutex);  
  8.     for (List<SnifferFunc>::iterator it = gSniffers.begin();  
  9.          it != gSniffers.end(); ++it) {  
  10.         String8 newMimeType;  
  11.         float newConfidence;  
  12.         sp<AMessage> newMeta;  
  13.         if ((*it)(this, &newMimeType, &newConfidence, &newMeta)) {  
  14.             if (newConfidence > *confidence) {  
  15.                 *mimeType = newMimeType;  
  16.                 *confidence = newConfidence;  
  17.                 *meta = newMeta;  
  18.             }  
  19.         }  
  20.     }  
  21.   
  22.     return *confidence > 0.0;  
  23. }  


gSniffers是一个系统支持的一些媒体格式的嗅探器列表,函数的作用就是用这些嗅探器一个一个的试,并给出一个confidence 信任值,也就是说值越高,它就越可能是这种格式。而这些嗅探器是在StagefrightMetadataRetriever的构造函数中注册的    DataSource::RegisterDefaultSniffers();

我们可以挑选其中的SniffMPEG4来看看,嗅探器都是在对应格式的文件解析器中,SniffMPEG4在MPEG4Extractor中。

[cpp] view plain copy
  1. bool SniffMPEG4(  
  2.         const sp<DataSource> &source, String8 *mimeType, float *confidence,  
  3.         sp<AMessage> *) {  
  4.     if (BetterSniffMPEG4(source, mimeType, confidence)) {  
  5.         return true;  
  6.     }  
  7.   
  8.     if (LegacySniffMPEG4(source, mimeType, confidence)) {  
  9.         LOGW(”Identified supported mpeg4 through LegacySniffMPEG4.”);  
  10.         return true;  
  11.     }  
  12.   
  13.     const char LegacyAtom[][8]={  
  14.         {”moov”},  
  15.         {”mvhd”},  
  16.         {”trak”},  
  17.      };  
  18.     uint8_t header[12];  
  19.     if (source->readAt(0, header, 12) != 12){  
  20.             return false;  
  21.     }  
  22.     for(int i=0; i<sizeof(LegacyAtom)/sizeof(LegacyAtom[0]);i++){  
  23.         if (!memcmp(LegacyAtom[i], &header[4], strlen(LegacyAtom[i]))) {  
  24.          *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;  
  25.             *confidence = 0.4f;  
  26.             return true;  
  27.         }  
  28.     }  
  29.   
  30.     return false;  
  31. }  


这里面涉及到几个函数BetterSniffMPEG4   LegacySniffMPEG4 这实际是一步一步来根据MEPG4的格式来试探这个文件是否符合MPEG4。

在这就不多讲了,想看懂这两个函数,你必须看MPEG4格式的标准文档。

拿到打分后也就知道了media的format,然后就根据这个类型创建一个MediaExtractor。回到STEP14,mRetriever->setDataSource(path)所有动作已经完成,总结下就是根据Android设备支持的媒体格式一个一个的试,然后得到一个和该文件最相符的格式。到此实际解析工作已经做完,下面一步是将得到的格式会送给Java层,

[cpp] view plain copy
  1. if ((value = mRetriever->extractMetadata(  
  2.                         METADATA_KEY_MIMETYPE)) != NULL) {  
  3.             client.setMimeType(value);  
  4.         }  



就是完成这个动作。然后有一个kKeyMap,这些实际就是我们需要存储于数据库中的媒体信息,包括时长,专辑之类的东西。也是通过mRetriever->extractMetadata函数得到。

STEP18

[cpp] view plain copy
  1. const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {  
  2.     if (mExtractor == NULL) {  
  3.         return NULL;  
  4.     }  
  5.   
  6.     if (!mParsedMetaData) {  
  7.         parseMetaData();  
  8.   
  9.         mParsedMetaData = true;  
  10.     }  
  11.   
  12.     ssize_t index = mMetaData.indexOfKey(keyCode);  
  13.   
  14.     if (index < 0) {  
  15.         return NULL;  
  16.     }  
  17.   
  18.     return strdup(mMetaData.valueAt(index).string());  



首次调用肯定是进入parseMetaData,这个函数完成解析工作,将所有需要的媒体信息全部从文件中读出来并保存到mMetaData这个键值对数组中。

具体解析工作也不说了,跟具体的格式相关。

下面最重要的一步就是将解析后得到的信息反馈给Java层client.addStringTag(kKeyMap[i].tag, value);

STEP19

[cpp] view plain copy
  1. bool MediaScannerClient::addStringTag(const char* name, const char* value)  
  2. {  
  3.     if (mLocaleEncoding != kEncodingNone) {  
  4.         // don’t bother caching strings that are all ASCII.  
  5.         // call handleStringTag directly instead.  
  6.         // check to see if value (which should be utf8) has any non-ASCII characters  
  7.         bool nonAscii = false;  
  8.         const char* chp = value;  
  9.         char ch;  
  10.         while ((ch = *chp++)) {  
  11.             if (ch & 0x80) {  
  12.                 nonAscii = true;  
  13.                 break;  
  14.             }  
  15.         }  
  16.   
  17.         if (nonAscii) {  
  18.             // save the strings for later so they can be used for native encoding detection  
  19.             mNames->push_back(name);  
  20.             mValues->push_back(value);  
  21.             return true;  
  22.         }  
  23.         // else fall through  
  24.     }  
  25.   
  26.     // autodetection is not necessary, so no need to cache the values  
  27.     // pass directly to the client instead  
  28.     return handleStringTag(name, value);  
  29. }  


直接看handleStringTag根据上面的经验,直接看java层的MyMediaScannerClient的handleStringTag函数

STEP 20

[java] view plain copy
  1. public void handleStringTag(String name, String value) {  
  2.             if (name.equalsIgnoreCase(“title”) || name.startsWith(“title;”)) {  
  3.                 // Don’t trim() here, to preserve the special \001 character  
  4.                 // used to force sorting. The media provider will trim() before  
  5.                 // inserting the title in to the database.  
  6.                 mTitle = value;  
  7.             } else if (name.equalsIgnoreCase(“artist”) || name.startsWith(“artist;”)) {  
  8.                 mArtist = value.trim();  
  9.             } else if (name.equalsIgnoreCase(“albumartist”) || name.startsWith(“albumartist;”)) {  
  10.                 mAlbumArtist = value.trim();  
  11.             } else if (name.equalsIgnoreCase(“album”) || name.startsWith(“album;”)) {  
  12.                 mAlbum = value.trim();  
  13.             } else if (name.equalsIgnoreCase(“composer”) || name.startsWith(“composer;”)) {  
  14.                 mComposer = value.trim();  
  15.             } else if (name.equalsIgnoreCase(“genre”) || name.startsWith(“genre;”)) {  
  16.                 // handle numeric genres, which PV sometimes encodes like ”(20)”  
  17.                 if (value.length() > 0) {  
  18.                     int genreCode = -1;  
  19.                     char ch = value.charAt(0);  
  20.                     if (ch == ‘(‘) {  
  21.                         genreCode = parseSubstring(value, 1, -1);  
  22.                     } else if (ch >= ‘0’ && ch <= ‘9’) {  
  23.                         genreCode = parseSubstring(value, 0, -1);  
  24.                     }  
  25.                     if (genreCode >= 0 && genreCode < ID3_GENRES.length) {  
  26.                         value = ID3_GENRES[genreCode];  
  27.                     } else if (genreCode == 255) {  
  28.                         // 255 is defined to be unknown  
  29.                         value = null;  
  30.                     }  
  31.                 }  
  32.                 mGenre = value;  
  33.             } else if (name.equalsIgnoreCase(“year”) || name.startsWith(“year;”)) {  
  34.                 mYear = parseSubstring(value, 00);  
  35.             } else if (name.equalsIgnoreCase(“tracknumber”) || name.startsWith(“tracknumber;”)) {  
  36.                 // track number might be of the form “2/12”  
  37.                 // we just read the number before the slash  
  38.                 int num = parseSubstring(value, 00);  
  39.                 mTrack = (mTrack / 1000) * 1000 + num;  
  40.             } else if (name.equalsIgnoreCase(“discnumber”) ||  
  41.                     name.equals(”set”) || name.startsWith(“set;”)) {  
  42.                 // set number might be of the form “1/3”  
  43.                 // we just read the number before the slash  
  44.                 int num = parseSubstring(value, 00);  
  45.                 mTrack = (num * 1000) + (mTrack % 1000);  
  46.             } else if (name.equalsIgnoreCase(“duration”)) {  
  47.                 mDuration = parseSubstring(value, 00);  
  48.             } else if (name.equalsIgnoreCase(“writer”) || name.startsWith(“writer;”)) {  
  49.                 mWriter = value.trim();  
  50.             } else if (name.equalsIgnoreCase(“compilation”)) {  
  51.                 mCompilation = parseSubstring(value, 00);  
  52.             }  
  53.         }  


这里并没有直接写数据库,而是暂时保存在成员变量中。接着回到STEP 11中,我们说到doScanFile的processFile,将文件解析处理并将信息上报上来存到成员变量中后,最后一步result = endFile(entry, ringtones, notifications, alarms, music, podcasts);肯定就得写数据库了。

STEP21

[java] view plain copy
  1. private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,  
  2.                 boolean alarms, boolean music, boolean podcasts)  
  3.                 throws RemoteException {  
  4.           . . .  
  5. }  



此函数篇幅太长,就不贴出来。有兴趣的同学可以仔细瞧瞧这段代码,它就是将前面解析出来的信息通过MediaProvider写入数据库的。

当然,写入的时候会区分成好多个表,每个表都有不同的列,有兴趣可以adb shell 进入你的Android手机看看/data/data/com.android.providers.media/databases这个目录下面是不是有几个数据库,internel.db、external.db等如果你熟练sqlite3命令可以看下这些数据库中的内容,我看了下我手机中的外置卡数据库中有哪些表

[plain] view plain copy
  1. sqlite> .tables  
  2. .tables  
  3. album_art            audio                search  
  4. album_info           audio_genres         searchhelpertitle  
  5. albums               audio_genres_map     thumbnails  
  6. android_metadata     audio_meta           video  
  7. artist_info          audio_playlists      videothumbnails  
  8. artists              audio_playlists_map  
  9. artists_albums_map   images  


看看有这么多,分别存储了不同种类的信息。

至于数据库的操作以及ContentProvider的使用就不多说了,下面总结如下:

系统开机或者收到挂载消息后,MediaProvider程序会扫描sdcard(内外置区分),根据系统支持的文件类型的后缀先将文件过滤一遍,将得到的符合条件的文件再深入读文件内容解析,看到底是什么格式,并且将文件的一些重要信息读取出来,最后保存于数据库中,方便其他应用程序使用。我分析的是原生的android2.3的代码,可能其他版本有所改变。这样看的话,如果你把文件的后缀名改一下系统也许就扫描不出来了哦,大家可以试试!!!

0 0
原创粉丝点击