上篇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> MediaExtractor::Create(
const sp<DataSource> &source, const char *mime) {
sp<AMessage> 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<AMessage> *meta) {
*mimeType = "";
*confidence = 0.0f;
meta->clear(); Mutex::Autolock autoLock(gSnifferMutex);
for (List<SnifferFunc>::iterator it = gSniffers.begin();
it != gSniffers.end(); ++it) {
String8 newMimeType;
float newConfidence;
sp<AMessage> 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<DataSource> &source, String8 *mimeType, float *confidence,
sp<AMessage> *) {
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<sizeof(LegacyAtom)/sizeof(LegacyAtom[0]);i++){
if (!memcmp(LegacyAtom[i], &header[4], strlen(LegacyAtom[i]))) {
*mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
*confidence = 0.4f;
return true;
}
} return false;
}

这里面涉及到几个函数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
.tables
album_art audio search
album_info audio_genres searchhelpertitle
albums audio_genres_map thumbnails
android_metadata audio_meta video
artist_info audio_playlists videothumbnails
artists audio_playlists_map
artists_albums_map images

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

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

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

Android媒体扫描详细解析之二(MediaScanner & MediaProvider)的更多相关文章

  1. Android媒体扫描详细解析之一(MediaScanner & MediaProvider)

    用过Android手机的同学都知道,每次开机的时候系统会先扫描sdcard,sdcard重新插拔(挂载)也会扫描一次sdcard. 为什么要扫描sdcard,其实是为了给系统的其他应用提供便利,比如, ...

  2. 目标检测从入门到精通—R-CNN详细解析(二)

    R-CNN目标检测详细解析 <Rich feature hierarchies for Accurate Object Detection and Segmentation> Author ...

  3. Android开发之 Android应用程序详细解析

    我们继续的沿用上一篇所建立的应用. Android应用程序可以分为:应用程序源代码(.java),应用程序描述文件(.xml),各种资源. 可以这么理解: 安卓应用程序,通过java代码来实现其业务逻 ...

  4. Android之使用XMLPull解析xml(二)

    转自:http://www.blogjava.net/sxyx2008/archive/2010/08/04/327885.html 介绍下在Android中极力推荐的xmlpull方式解析xml.x ...

  5. Android Data Binding语法解析(二)

    上篇我们知道了Data Binding的最简单的用法,那么Data Binding其中最为重要也是最复杂的其实就是在xml布局文件中给对应的控件进行数据绑定了,接下来就一一说明Data Binding ...

  6. Anroid 手机助手 详细解析 概述(二)

    这篇主要说一下手机插入之后的一些动作. 1)  捕获窗口消息 插入拔出一个USB设备windows 会给所有的窗口发送特定的消息,只要我们捕获这些消息就可以处理设备插入和拔出.需要注意的是插入或者拔出 ...

  7. Android 开源项目android-open-project解析之(二) GridView,ImageView,ProgressBar,TextView

    五.GridView StaggeredGridView 同意非对齐行的GridView,类似Pinterest的瀑布流.而且跟ListView一样自带View缓存,继承自ViewGroup 项目地址 ...

  8. Android开发MVP模式解析

    http://www.cnblogs.com/bravestarrhu/archive/2012/05/02/2479461.html 在开发Android应用时,相信很多同学遇到和我一样的情况,虽然 ...

  9. 深入Android媒体存储服务(二):磁盘扫描流程

    简介: 本文是<深入Android媒体存储服务>系列第二篇,简要介绍媒体存储服务扫描文件的流程.文中介绍的是 Android 4.2. Android 有一套媒体存储服务,进程名是 and ...

随机推荐

  1. python @classmethod 的使用场合

    python @classmethod 的使用场合 官方的说法: classmethod(function)中文说明:classmethod是用来指定一个类的方法为类方法,没有此参数指定的类的方法为实 ...

  2. 比较IBM MQSeries和BEA WebLogic JMS Server(转载)

    在面向消息的中间件(MOM)这个领域,IBM MQSeries (又称WebSphere MQ)一直是当仁不让的超级大哥,其它还有一些小兄弟,比如SwiftMQ.SonicMQ之类.但近年来随着J2E ...

  3. BZOJ1017 魔兽地图DotR (树上背包)

    一道背包的神题,用到了树上dp和背包dp,这个题的特殊性在于儿子对于父亲节点是有影响的,所以用f[i][j][k]表示第i号装备,其中用j个来合成上层装备,花费k元所能获得最大的力量值. 然后对于每一 ...

  4. NOI1999 JZYZOJ1289 棋盘分割 dp 方差的数学结论

    http://172.20.6.3/Problem_Show.asp?id=1289 除了下标一坨一坨屎一样挺恶心其他都还挺容易的dp,这道题才发现scanf保留小数位是四舍五入的,惊了. f[k][ ...

  5. OpenSSL Heartbleed “心脏滴血”漏洞简单攻击示例

    OpenSSL Heartbleed漏洞的公开和流行让许多人兴奋了一把,也让另一些人惊慌了一把. 单纯从攻击的角度讲,我已知道的,网上公开的扫描工具有: 1.  Nmap脚本ssl-heartblee ...

  6. Codeforces Beta Round #8 A. Train and Peter KMP

    A. Train and Peter 题目连接: http://www.codeforces.com/contest/8/problem/A Description Peter likes to tr ...

  7. VMware中网络设置之host-only

    有了前面一篇的NAT的网络设置,本文就显得非常简单了.同样图文结合的步骤: 1.设置host-only模式. 2.设置linux虚拟机的静态IP.进入linux系统,点击主菜单---系统设置---网络 ...

  8. mysql 绿色版安装

    1. 下载MySql5.6.10GA解压缩版,这就不多说也不上图了,下不到或者下好之后不知道如何解压的接下去的文章也没什么好多看的. 2. 解压好之后进入根目录是这样个情况(本人使用的是MySql5. ...

  9. char *p 与char p[]

    char *p=a和char*p=&a 前者p是一个指针变量后者p是一个char型变量 char *p = "123"; 为全局数组,放在常量区,而非普通数据段(静态存储区 ...

  10. ext:grid分页,列宽度自动填满grid宽度

    var cm = new Ext.grid.ColumnModel([{      header : '编号',      dataIndex : 'id'     }, {      header ...