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

status_t StagefrightMediaScanner::processFile(        const char *path, const char *mimeType,        MediaScannerClient &client) {    LOGV("processFile '%s'.", path);    client.setLocale(locale());    client.beginFile();    const char *extension = strrchr(path, '.');    if (!extension) {        return UNKNOWN_ERROR;    }    if (!FileHasAcceptableExtension(extension)) {        client.endFile();        return UNKNOWN_ERROR;    }    if (!strcasecmp(extension, ".mid")            || !strcasecmp(extension, ".smf")            || !strcasecmp(extension, ".imy")            || !strcasecmp(extension, ".midi")            || !strcasecmp(extension, ".xmf")            || !strcasecmp(extension, ".rtttl")            || !strcasecmp(extension, ".rtx")            || !strcasecmp(extension, ".ota")) {        return HandleMIDI(path, &client);    }    if (mRetriever->setDataSource(path) == OK) {        const char *value;        if ((value = mRetriever->extractMetadata(                        METADATA_KEY_MIMETYPE)) != NULL) {            client.setMimeType(value);        }        struct KeyMap {            const char *tag;            int key;        };        static const KeyMap kKeyMap[] = {            { "tracknumber", METADATA_KEY_CD_TRACK_NUMBER },            { "discnumber", METADATA_KEY_DISC_NUMBER },            { "album", METADATA_KEY_ALBUM },            { "artist", METADATA_KEY_ARTIST },            { "albumartist", METADATA_KEY_ALBUMARTIST },            { "composer", METADATA_KEY_COMPOSER },            { "genre", METADATA_KEY_GENRE },            { "title", METADATA_KEY_TITLE },            { "year", METADATA_KEY_YEAR },            { "duration", METADATA_KEY_DURATION },            { "writer", METADATA_KEY_WRITER },            { "compilation", METADATA_KEY_COMPILATION },        };        static const size_t kNumEntries = sizeof(kKeyMap) / sizeof(kKeyMap[0]);        for (size_t i = 0; i < kNumEntries; ++i) {            const char *value;            if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) != NULL) {                client.addStringTag(kKeyMap[i].tag, value);            }        }    }    client.endFile();    return OK;}
来进行代码跟进说明,首先StagefrightMediaScanner是Stagefright的一部分,它负责媒体扫描工作,而stagefright是整个android系统media处理的框架,包括音视频的播放。

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

StagefrightMediaScanner::StagefrightMediaScanner()    : mRetriever(new MediaMetadataRetriever) {}
STEP15

status_t MediaMetadataRetriever::setDataSource(const char* srcUrl){    LOGV("setDataSource");    Mutex::Autolock _l(mLock);    if (mRetriever == 0) {        LOGE("retriever is not initialized");        return INVALID_OPERATION;    }    if (srcUrl == NULL) {        LOGE("data source is a null pointer");        return UNKNOWN_ERROR;    }    LOGV("data source (%s)", srcUrl);    return mRetriever->setDataSource(srcUrl);}

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

STEP15

status_t StagefrightMetadataRetriever::setDataSource(const char *uri) {    LOGV("setDataSource(%s)", uri);    mParsedMetaData = false;    mMetaData.clear();    delete mAlbumArt;    mAlbumArt = NULL;    mSource = DataSource::CreateFromURI(uri);    if (mSource == NULL) {        return UNKNOWN_ERROR;    }    mExtractor = MediaExtractor::Create(mSource);    if (mExtractor == NULL) {        mSource.clear();        return UNKNOWN_ERROR;    }    return OK;}

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

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

STEP16

sp MediaExtractor::Create(        const sp &source, const char *mime) {    sp meta;    String8 tmp;    if (mime == NULL) {        float confidence;        if (!source->sniff(&tmp, &confidence, &meta)) {            LOGV("FAILED to autodetect media content.");            return NULL;        }        mime = tmp.string();        LOGV("Autodetected media content as '%s' with confidence %.2f",             mime, confidence);    }    if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)            || !strcasecmp(mime, "audio/mp4")) {        return new MPEG4Extractor(source);    } ...} ...}
这里面有一个很关键的函数source->sniff(&tmp, &confidence, &meta),字面上看就是嗅探的意思,非常形象,DataSource里面有一个sniff函数

STEP 17

bool DataSource::sniff(        String8 *mimeType, float *confidence, sp *meta) {    *mimeType = "";    *confidence = 0.0f;    meta->clear();    Mutex::Autolock autoLock(gSnifferMutex);    for (List::iterator it = gSniffers.begin();         it != gSniffers.end(); ++it) {        String8 newMimeType;        float newConfidence;        sp newMeta;        if ((*it)(this, &newMimeType, &newConfidence, &newMeta)) {            if (newConfidence > *confidence) {                *mimeType = newMimeType;                *confidence = newConfidence;                *meta = newMeta;            }        }    }    return *confidence > 0.0;}
gSniffers是一个系统支持的一些媒体格式的嗅探器列表,函数的作用就是用这些嗅探器一个一个的试,并给出一个confidence 信任值,也就是说值越高,它就越可能是这种格式。而这些嗅探器是在StagefrightMetadataRetriever的构造函数中注册的    DataSource::RegisterDefaultSniffers();

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

bool SniffMPEG4(        const sp &source, String8 *mimeType, float *confidence,        sp *) {    if (BetterSniffMPEG4(source, mimeType, confidence)) {        return true;    }    if (LegacySniffMPEG4(source, mimeType, confidence)) {        LOGW("Identified supported mpeg4 through LegacySniffMPEG4.");        return true;    }    const char LegacyAtom[][8]={        {"moov"},        {"mvhd"},        {"trak"},     };    uint8_t header[12];    if (source->readAt(0, header, 12) != 12){            return false;    }    for(int i=0; i
这里面涉及到几个函数BetterSniffMPEG4   LegacySniffMPEG4 这实际是一步一步来根据MEPG4的格式来试探这个文件是否符合MPEG4。

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

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

if ((value = mRetriever->extractMetadata(                        METADATA_KEY_MIMETYPE)) != NULL) {            client.setMimeType(value);        }

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

STEP18

const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {    if (mExtractor == NULL) {        return NULL;    }    if (!mParsedMetaData) {        parseMetaData();        mParsedMetaData = true;    }    ssize_t index = mMetaData.indexOfKey(keyCode);    if (index < 0) {        return NULL;    }    return strdup(mMetaData.valueAt(index).string());

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

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

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

STEP19

bool MediaScannerClient::addStringTag(const char* name, const char* value){    if (mLocaleEncoding != kEncodingNone) {        // don't bother caching strings that are all ASCII.        // call handleStringTag directly instead.        // check to see if value (which should be utf8) has any non-ASCII characters        bool nonAscii = false;        const char* chp = value;        char ch;        while ((ch = *chp++)) {            if (ch & 0x80) {                nonAscii = true;                break;            }        }        if (nonAscii) {            // save the strings for later so they can be used for native encoding detection            mNames->push_back(name);            mValues->push_back(value);            return true;        }        // else fall through    }    // autodetection is not necessary, so no need to cache the values    // pass directly to the client instead    return handleStringTag(name, value);}
直接看handleStringTag根据上面的经验,直接看java层的MyMediaScannerClient的 handleStringTag函数

STEP 20

public void handleStringTag(String name, String value) {            if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {                // Don't trim() here, to preserve the special \001 character                // used to force sorting. The media provider will trim() before                // inserting the title in to the database.                mTitle = value;            } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {                mArtist = value.trim();            } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")) {                mAlbumArtist = value.trim();            } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {                mAlbum = value.trim();            } else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {                mComposer = value.trim();            } else if (name.equalsIgnoreCase("genre") || name.startsWith("genre;")) {                // handle numeric genres, which PV sometimes encodes like "(20)"                if (value.length() > 0) {                    int genreCode = -1;                    char ch = value.charAt(0);                    if (ch == '(') {                        genreCode = parseSubstring(value, 1, -1);                    } else if (ch >= '0' && ch <= '9') {                        genreCode = parseSubstring(value, 0, -1);                    }                    if (genreCode >= 0 && genreCode < ID3_GENRES.length) {                        value = ID3_GENRES[genreCode];                    } else if (genreCode == 255) {                        // 255 is defined to be unknown                        value = null;                    }                }                mGenre = value;            } else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {                mYear = parseSubstring(value, 0, 0);            } else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {                // track number might be of the form "2/12"                // we just read the number before the slash                int num = parseSubstring(value, 0, 0);                mTrack = (mTrack / 1000) * 1000 + num;            } else if (name.equalsIgnoreCase("discnumber") ||                    name.equals("set") || name.startsWith("set;")) {                // set number might be of the form "1/3"                // we just read the number before the slash                int num = parseSubstring(value, 0, 0);                mTrack = (num * 1000) + (mTrack % 1000);            } else if (name.equalsIgnoreCase("duration")) {                mDuration = parseSubstring(value, 0, 0);            } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {                mWriter = value.trim();            } else if (name.equalsIgnoreCase("compilation")) {                mCompilation = parseSubstring(value, 0, 0);            }        }
这里并没有直接写数据库,而是暂时保存在成员变量中。接着回到 STEP 11中,我们说到doScanFile的processFile,将文件解析处理并将信息上报上来存到成员变量中后,最后一步result = endFile(entry, ringtones, notifications, alarms, music, podcasts);肯定就得写数据库了。

STEP21

private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,                boolean alarms, boolean music, boolean podcasts)                throws RemoteException {          . . .}

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

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

sqlite> .tables.tablesalbum_art            audio                searchalbum_info           audio_genres         searchhelpertitlealbums               audio_genres_map     thumbnailsandroid_metadata     audio_meta           videoartist_info          audio_playlists      videothumbnailsartists              audio_playlists_mapartists_albums_map   images
看看有这么多,分别存储了不同种类的信息。

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

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



更多相关文章

  1. 简易音乐播放器(Android(安卓)Studio)
  2. android studio 3.5.1 使用第三方.so 库
  3. Android(安卓)Hook框架Xposed详解:从源代码分析到开发指南
  4. GradientDrawable动态改变Shape文件
  5. android_studio连接夜神模拟器
  6. Android在线更新 远程安装程序
  7. android 换肤
  8. Android(安卓)ART dex2oat 浅析
  9. android 背光驱动

随机推荐

  1. 【Android】高德定位错误总结
  2. 【Android快捷开发笔记系列】——Data Bi
  3. Android中AsyncTask的处理后台耗时操作(如
  4. Android开发的硬件加速
  5. Android平台实现双向证书(https)验证简单
  6. 【android x86 5.1】system/core/目录下R
  7. Android(安卓)LocalBroadcastReceiver本
  8. Android(安卓)Studio录制手机屏幕并制作G
  9. android仿腾讯安全管家首页抽屉效果
  10. android版PDA通过USB与.net应用程序通讯,