Android开发之媒体扫描详细解析(下)
来源:互联网 发布:php开发好学吗 编辑:程序博客网 时间:2024/05/15 12:43
上篇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开发之媒体扫描详细解析(下)
- Android开发之媒体扫描详细解析(下)
- Android开发之媒体扫描详细解析(上)
- Android开发之媒体扫描详细解析(上)
- Android媒体扫描详细解析之二(MediaScanner & MediaProvider)
- Android媒体扫描详细解析之一(MediaScanner & MediaProvider)
- android媒体扫描分享(1)-20121229
- Android 媒体扫描MediaScanner
- Android媒体扫描
- Android 媒体开发:YUV格式解析
- Android 媒体开发:AVI视频格式解析
- Android媒体扫描代码分析
- Android如何屏蔽媒体扫描
- Android媒体扫描代码分析
- Android Service服务详细解析(下)
- Android开发之二维码扫描
- 媒体MediaPlayer错误详细解析
- Android媒体开发之音乐播放…
- linux 源码安装SecureCRT客户端的rz上传和sz下载命令
- python数字图像处理(9):直方图与均衡化
- 【easyui】datagrid中增加按钮样式
- SQL操作数据库
- gdb来debug caffe代码
- Android开发之媒体扫描详细解析(下)
- Cadence元件与走线一起移动的方法
- Emgu Mat方法和Image方法设置一张背景图像
- 阳光很舒服
- Linux seq命令笔记
- 吃糖果(组合数学)
- linux下mysql的安装
- Emgu OpenFileDialog()打开图像,分别用Image和Mat CvInvoke.Imread 加载
- (OK) Android中集成第三方库的方法和问题