前言

MediaScanner是Android系统中针对媒体文件的扫描过程,将储存空间中的媒体文件通过扫描的方式遍历并存储在数据库中,然后通过MediaProvider提供接口使用,在Android多媒体中占有很重要的位置。

源码位置

packages\providers\mediaprovider

frameworks\base\media\java\android\media

frameworks\av\media\libmedia

frameworks\base\media\jni

frameworks\av\media\libstagefright

扫描过程

MediaScannerReceiver.java (packages\providers\mediaprovider\src\com\android\providers\media)

MediaScannerReceiver继承BroadcastReceiver,在AndroidManifest.xml静态注册

       <receiver android:name="MediaScannerReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_UNMOUNTED" />
<data android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
<data android:scheme="file" />
</intent-filter>
</receiver>

可以看到,当接收到

1. android.intent.action.BOOT_COMPLETED

2. android.intent.action.MEDIA_MOUNTED

3. android.intent.action.MEDIA_UNMOUNTED

4. android.intent.action.MEDIA_SCANNER_SCAN_FILE

广播的时候,MediaScannerReceiver中的onReceiver()方法将会执行

public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final Uri uri = intent.getData();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
// Scan both internal and external storage
scan(context, MediaProvider.INTERNAL_VOLUME);
scan(context, MediaProvider.EXTERNAL_VOLUME); } else {
if (uri.getScheme().equals("file")) {
// handle intents related to external storage
String path = uri.getPath();
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath(); try {
path = new File(path).getCanonicalPath();
} catch (IOException e) {
Log.e(TAG, "couldn't canonicalize " + path);
return;
}
if (path.startsWith(legacyPath)) {
path = externalStoragePath + path.substring(legacyPath.length());
} Log.d(TAG, "action: " + action + " path: " + path);
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
// scan whenever any volume is mounted
scan(context, MediaProvider.EXTERNAL_VOLUME);
} else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
path != null && path.startsWith(externalStoragePath + "/")) {
scanFile(context, path);
}
}
}
}

onReceiver方法中,会执行到scan()或者是scanFile()方法,一个是针对整个卷,一个是针对具体的文件,从传入的参数可以看出眉目,scan传入的是卷名,scanFile传入的是文件路径。通过接收广播启动扫描,然而不管是scan还是scanFile都是以startService的方式启动MediaScannerService。

    private void scan(Context context, String volume) {
Bundle args = new Bundle();
args.putString("volume", volume);
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
} private void scanFile(Context context, String path) {
Bundle args = new Bundle();
args.putString("filepath", path);
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
}

分别传入卷名或者文件路径。

MediaScannerService.java (packages\providers\mediaprovider\src\com\android\providers\media)

MediaScannerService看下AndroidManifest.xml文件中定义

        <service android:name="MediaScannerService" android:exported="true">
<intent-filter>
<action android:name="android.media.IMediaScannerService" />
</intent-filter>
</service>

继承Service,实现Runnable接口,首先执行onCreate()方法

    public void onCreate()
{
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
StorageManager storageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
mExternalStoragePaths = storageManager.getVolumePaths(); // Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block.
Thread thr = new Thread(null, this, "MediaScannerService");
thr.start();
}

这里会单独启动一个线程来处理,然后走到onStartCommand()

    public int onStartCommand(Intent intent, int flags, int startId)
{
while (mServiceHandler == null) {
synchronized (this) {
try {
wait(100);
} catch (InterruptedException e) {
}
}
} if (intent == null) {
Log.e(TAG, "Intent is null in onStartCommand: ",
new NullPointerException());
return Service.START_NOT_STICKY;
} Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent.getExtras();
mServiceHandler.sendMessage(msg); // Try again later if we are killed before we can finish scanning.
return Service.START_REDELIVER_INTENT;
}

这里先等待mServiceHandler创建完成,然后通过handler message机制传递消息给handler处理,这里的mServiceHandler创建过程是在run()方法中

    public void run()
{
// reduce priority below other background threads to avoid interfering
// with other services at boot time.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +
Process.THREAD_PRIORITY_LESS_FAVORABLE);
Looper.prepare(); mServiceLooper = Looper.myLooper();
mServiceHandler = new ServiceHandler(); Looper.loop();
}

由于这个是单独的线程,无法保证在发送消息的时候,handler已经创建完成,所以需要等待handler的创建

看下这个处理消息的handler

    private final class ServiceHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
Bundle arguments = (Bundle) msg.obj;
String filePath = arguments.getString("filepath"); try {
if (filePath != null) {
IBinder binder = arguments.getIBinder("listener");
IMediaScannerListener listener =
(binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));
Uri uri = null;
try {
uri = scanFile(filePath, arguments.getString("mimetype"));
} catch (Exception e) {
Log.e(TAG, "Exception scanning file", e);
}
if (listener != null) {
listener.scanCompleted(filePath, uri);
}
} else {
String volume = arguments.getString("volume");
String[] directories = null; if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
// scan internal media storage
directories = new String[] {
Environment.getRootDirectory() + "/media",
Environment.getOemDirectory() + "/media",
};
}
else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
// scan external storage volumes
directories = mExternalStoragePaths;
} if (directories != null) {
if (false) Log.d(TAG, "start scanning volume " + volume + ": "
+ Arrays.toString(directories));
scan(directories, volume);
if (false) Log.d(TAG, "done scanning volume " + volume);
}
}
} catch (Exception e) {
Log.e(TAG, "Exception in handleMessage", e);
} stopSelf(msg.arg1);
}
};

分别执行scan和scanFile方法

    private void scan(String[] directories, String volumeName) {
Uri uri = Uri.parse("file://" + directories[0]);
// don't sleep while scanning
mWakeLock.acquire(); try {
ContentValues values = new ContentValues();
values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values); sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri)); try {
if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
openDatabase(volumeName);
} MediaScanner scanner = createMediaScanner();
scanner.scanDirectories(directories, volumeName);
} catch (Exception e) {
Log.e(TAG, "exception in MediaScanner.scan()", e);
} getContentResolver().delete(scanUri, null, null); } finally {
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
mWakeLock.release();
}
}
    private Uri scanFile(String path, String mimeType) {
String volumeName = MediaProvider.EXTERNAL_VOLUME;
openDatabase(volumeName);
MediaScanner scanner = createMediaScanner();
try {
// make sure the file path is in canonical form
String canonicalPath = new File(path).getCanonicalPath();
return scanner.scanSingleFile(canonicalPath, volumeName, mimeType);
} catch (Exception e) {
Log.e(TAG, "bad path " + path + " in scanFile()", e);
return null;
}
}

两者都是会通过createMediaScanner创建MediaScanner对象,然后通过scanDirectories或者scanSingleFile来对卷或者文件进行扫描,下面我们就只针对scanDirectories进行分析。

MediaScanner.java (base\media\java\android\media)

  public void scanDirectories(String[] directories, String volumeName) {
try {
long start = System.currentTimeMillis();
initialize(volumeName);//初始化数据库中的一些内容
prescan(null, true); //预扫描
long prescan = System.currentTimeMillis(); //计算预扫描时间 if (ENABLE_BULK_INSERTS) {
// create MediaInserter for bulk inserts
mMediaInserter = new MediaInserter(mMediaProvider, mPackageName, 500);
} for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], mClient); //扫描目录
} if (ENABLE_BULK_INSERTS) {
// flush remaining inserts
mMediaInserter.flushAll();
mMediaInserter = null;
} long scan = System.currentTimeMillis();
postscan(directories);
long end = System.currentTimeMillis(); if (false) {
Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n");
Log.d(TAG, "postscan time: " + (end - scan) + "ms\n");
Log.d(TAG, " total time: " + (end - start) + "ms\n");
}
} catch (SQLException e) {
// this might happen if the SD card is removed while the media scanner is running
Log.e(TAG, "SQLException in MediaScanner.scan()", e);
} catch (UnsupportedOperationException e) {
// this might happen if the SD card is removed while the media scanner is running
Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
} finally {
releaseResources();
}
}

这里面比较重要的几个内容,一个是prescan,一个是processDirectory

先看看prescan

                Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build(); //查询条数为1000条
mWasEmptyPriorToScan = true; while (true) {
selectionArgs[0] = "" + lastId;
if (c != null) {
c.close();
c = null;
}
c = mMediaProvider.query(mPackageName, limitUri, FILES_PRESCAN_PROJECTION,
where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
if (c == null) {
break;
} int num = c.getCount(); if (num == 0) {
break;
}
mWasEmptyPriorToScan = false;
while (c.moveToNext()) {
long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
lastId = rowId;
//查询id,路径,格式和最后修改时间 // Only consider entries with absolute path names.
// This allows storing URIs in the database without the
// media scanner removing them.
if (path != null && path.startsWith("/")) {
boolean exists = false;
try {
exists = Os.access(path, android.system.OsConstants.F_OK);
} catch (ErrnoException e1) {
}
......
}
}
}
}
}

这里主要就是注意下最后修改时间,后面扫描的过程中应该会用到。

再看下processDirectory,native方法

android_media_MediaScanner.cpp (base\media\jni)

static void
android_media_MediaScanner_processDirectory(
JNIEnv *env, jobject thiz, jstring path, jobject client)
{
ALOGV("processDirectory");
MediaScanner *mp = getNativeScanner_l(env, thiz);
if (mp == NULL) {
jniThrowException(env, kRunTimeException, "No scanner available");
return;
} if (path == NULL) {
jniThrowException(env, kIllegalArgumentException, NULL);
return;
} const char *pathStr = env->GetStringUTFChars(path, NULL);
if (pathStr == NULL) { // Out of memory
return;
} MyMediaScannerClient myClient(env, client);
MediaScanResult result = mp->processDirectory(pathStr, myClient);
if (result == MEDIA_SCAN_RESULT_ERROR) {
ALOGE("An error occurred while scanning directory '%s'.", pathStr);
}
env->ReleaseStringUTFChars(path, pathStr);
}

从MediaScanner.java一直跟过来,忘记了MediaScanner.java中有一个比较重要的内容

    static {
System.loadLibrary("media_jni");
native_init();
}

这里加载libmedia_jni.so即上面会用到的android_media_MediaScanner.cpp就是存在于这个so中,

private static native final void native_init();
static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
ALOGV("native_init");
jclass clazz = env->FindClass(kClassMediaScanner);
//这里指的是static const char* const kClassMediaScanner ="android/media/MediaScanner";也就是后面会用到的MediaScanner.cpp
if (clazz == NULL) {
return;
} fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
}

在创建MediaScanner对象的过程中

    public MediaScanner(Context c) {
native_setup();
mContext = c;
mPackageName = c.getPackageName();
mBitmapOptions.inSampleSize = 1;
mBitmapOptions.inJustDecodeBounds = true; setDefaultRingtoneFileNames(); mExternalStoragePath = Environment.getExternalStorageDirectory().getAbsolutePath();
mExternalIsEmulated = Environment.isExternalStorageEmulated();
//mClient.testGenreNameConverter();
}
......
private native final void native_setup();//native方法
static void
android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
{
ALOGV("native_setup");
MediaScanner *mp = new StagefrightMediaScanner;//这里采用的是StagefrightMediaScanner if (mp == NULL) {
jniThrowException(env, kRunTimeException, "Out of memory");
return;
} env->SetLongField(thiz, fields.context, (jlong)mp);//set操作一下,后面会用到
}

再回到processDirectory方法中来,

MediaScanner *mp = getNativeScanner_l(env, thiz);拿到MediaScaner的cpp对象

MyMediaScannerClient myClient(env, client); //创建MyMediaScannerClient对象,而这里传入的client是MediaScanner.java中的MyMediaScannerClient对象

MediaScanResult result = mp->processDirectory(pathStr, myClient);

//执行processDirectory方法,传入myClient对象

这样传来传去无非就是想保存下当前的java侧的一个client,后面会有妙用?

StagefrightMediaScanner继承MediaScanner,没有实现processDirectory方法,使用的是父类中的方法

MediaScanResult MediaScanner::processDirectory(
const char *path, MediaScannerClient &client) {
......
MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false);
free(pathBuffer); return result;
}
MediaScanResult MediaScanner::doProcessDirectory(
char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) {
......
while ((entry = readdir(dir))) {
if (doProcessDirectoryEntry(path, pathRemaining, client, noMedia, entry, fileSpot)
== MEDIA_SCAN_RESULT_ERROR) {
result = MEDIA_SCAN_RESULT_ERROR;
break;
}
} }
MediaScanResult MediaScanner::doProcessDirectoryEntry(
char *path, int pathRemaining, MediaScannerClient &client, bool noMedia,
struct dirent* entry, char* fileSpot) {
......
if (type == DT_DIR) { //如果是目录,继续遍历子节点
bool childNoMedia = noMedia;
// set noMedia flag on directories with a name that starts with '.'
// for example, the Mac ".Trashes" directory
if (name[0] == '.')
childNoMedia = true; // report the directory to the client
if (stat(path, &statbuf) == 0) {
status_t status = client.scanFile(path, statbuf.st_mtime, 0,
true /*isDirectory*/, childNoMedia);
if (status) {
return MEDIA_SCAN_RESULT_ERROR;
}
} // and now process its contents
strcat(fileSpot, "/");
MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1,
client, childNoMedia);
if (result == MEDIA_SCAN_RESULT_ERROR) {
return MEDIA_SCAN_RESULT_ERROR;
}
} else if (type == DT_REG) { //如果是普通文件,执行scanFile操作
stat(path, &statbuf);
status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size,
false /*isDirectory*/, noMedia);
......
}

这里注意一下,使用的client就是之前传入的参数,最终会调用会MediaScanner.java中MyMediaScannerClient中的scanFile方法

        public void scanFile(String path, long lastModified, long fileSize,
boolean isDirectory, boolean noMedia) {
// This is the callback funtion from native codes.
// Log.v(TAG, "scanFile: "+path);
doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
}
       public Uri doScanFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
......
try {
FileEntry entry = beginFile(path, mimeType, lastModified,
fileSize, isDirectory, noMedia);
//beginFile创建FileEntry
......
// rescan for metadata if file was modified since last scan
if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
if (noMedia) {
result = endFile(entry, false, false, false, false, false);
} else {
......
// we only extract metadata for audio and video files
if (isaudio || isvideo) {
processFile(path, mimeType, this);//处理音频视频文件
} if (isimage) {
processImageFile(path);//处理图片文件
} result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
}
......
}

下面看下音频文件的处理

processFile(path, mimeType, this),注意这里传入的参数有路径和mimetype

private native void processFile(String path, String mimeType, MediaScannerClient client);

跟到android_media_MediaScanner.cpp

MyMediaScannerClient myClient(env, client);
MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);

然后跟到

StagefrightMediaScanner.cpp (av\include\media\stagefright)

MediaScanResult StagefrightMediaScanner::processFile(
const char *path, const char *mimeType,
MediaScannerClient &client) {
ALOGV("processFile '%s'.", path); client.setLocale(locale());
client.beginFile();
MediaScanResult result = processFileInternal(path, mimeType, client);
client.endFile();
return result;
}
MediaScanResult StagefrightMediaScanner::processFileInternal(
const char *path, const char * /* mimeType */,
MediaScannerClient &client) {
......
sp<MediaMetadataRetriever> mRetriever(new MediaMetadataRetriever);
//创建解析实例
int fd = open(path, O_RDONLY | O_LARGEFILE);
status_t status;
if (fd < 0) { //设置数据源
// couldn't open it locally, maybe the media server can?
status = mRetriever->setDataSource(NULL /* httpService */, path);
} else {
status = mRetriever->setDataSource(fd, 0, 0x7ffffffffffffffL);
close(fd);
}
......
const char *value;
if ((value = mRetriever->extractMetadata(//解析元数据
METADATA_KEY_MIMETYPE)) != NULL) {
status = client.setMimeType(value);
if (status) {
return MEDIA_SCAN_RESULT_ERROR;
}
} 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 },
{ "isdrm", METADATA_KEY_IS_DRM },
{ "width", METADATA_KEY_VIDEO_WIDTH },
{ "height", METADATA_KEY_VIDEO_HEIGHT },
};
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) {
status = client.addStringTag(kKeyMap[i].tag, value);
if (status != OK) {
return MEDIA_SCAN_RESULT_ERROR;
}
}
} return MEDIA_SCAN_RESULT_OK;
}

这里创建的MediaMetadataRetriever并不是MediaMetadataRetriver本身,而是一个之类,StagefrightMetadataRetriver,中间有一个过程,我们来看下。

MediaMetadataRetriever::MediaMetadataRetriever()
{
ALOGV("constructor");
const sp<IMediaPlayerService>& service(getService());
if (service == 0) {
ALOGE("failed to obtain MediaMetadataRetrieverService");
return;
}
sp<IMediaMetadataRetriever> retriever(service->createMetadataRetriever());
//创建元数据解析对象
if (retriever == 0) {
ALOGE("failed to create IMediaMetadataRetriever object from server");
}
mRetriever = retriever;//赋值给mRetriver
}

这里的service是MediaPlayerService,这里使用的代理,实现直接看MediaPlayerService.cpp中的函数

sp<IMediaMetadataRetriever> MediaPlayerService::createMetadataRetriever()
{
pid_t pid = IPCThreadState::self()->getCallingPid();
sp<MetadataRetrieverClient> retriever = new MetadataRetrieverClient(pid);
//这里创建了MetadataRetrieverClient对象
ALOGV("Create new media retriever from pid %d", pid);
return retriever;
}

MetadataRetrieverClient.cpp (av\media\libmediaplayerservice)

MetadataRetrieverClient::MetadataRetrieverClient(pid_t pid)
{
ALOGV("MetadataRetrieverClient constructor pid(%d)", pid);
mPid = pid;
mThumbnail = NULL;
mAlbumArt = NULL;
mRetriever = NULL;
}

显然上面创建的东西只是一个空壳,继续往下看

在processFileInternal函数中setDataSource函数中同样会跟到

status_t MetadataRetrieverClient::setDataSource(int fd, int64_t offset, int64_t length)
{
......
player_type playerType =
MediaPlayerFactory::getPlayerType(NULL /* client */,
fd,
offset,
length);
//获取playertype方便后面选择对应的元数据解析器
ALOGV("player type = %d", playerType);
sp<MediaMetadataRetrieverBase> p = createRetriever(playerType);//创建实际的metadataretriver
if (p == NULL) {
::close(fd);
return NO_INIT;
}
status_t status = p->setDataSource(fd, offset, length);
if (status == NO_ERROR) mRetriever = p;
::close(fd);
return status;
}
static sp<MediaMetadataRetrieverBase> createRetriever(player_type playerType)
{
sp<MediaMetadataRetrieverBase> p;
switch (playerType) {
case STAGEFRIGHT_PLAYER:
case NU_PLAYER:
{
p = new StagefrightMetadataRetriever;//这里才是我们实际使用的MedadataRetriver
break;
}
case SONIVOX_PLAYER:
ALOGV("create midi metadata retriever");
p = new MidiMetadataRetriever();
break;
default:
// TODO:
// support for TEST_PLAYER
ALOGE("player type %d is not supported", playerType);
break;
}
if (p == NULL) {
ALOGE("failed to create a retriever object");
}
return p;
}

StagefrightMetadataRetriever.cpp (av\media\libstagefright)

那么然后执行setDataSouce()

// Warning caller retains ownership of the filedescriptor! Dup it if necessary.
status_t StagefrightMetadataRetriever::setDataSource(
int fd, int64_t offset, int64_t length) {
......
mExtractor = MediaExtractor::Create(mSource);//创建媒体解析器
......
}

setDataSouce()有两个方法,传入的参数不用,另外一个是针对在线媒体的

然后就是创建媒体解析器

MediaExtractor.cpp (av\media\libstagefright)

sp<MediaExtractor> MediaExtractor::Create(
const sp<DataSource> &source, const char *mime) {
......
//如果Mimetype为null,匹配出最符合的mimetype
if (mime == NULL) {
float confidence;
if (!source->sniff(&tmp, &confidence, &meta)) {
ALOGV("FAILED to autodetect media content."); return NULL;
}
mime = tmp.string();
ALOGV("Autodetected media content as '%s' with confidence %.2f",
mime, confidence);
}
......
MediaExtractor *ret = NULL;
//不同的mimetype对应不同的extractor
if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
|| !strcasecmp(mime, "audio/mp4")) {
ret = new MPEG4Extractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
ret = new MP3Extractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
ret = new AMRExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
ret = new FLACExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
ret = new WAVExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
ret = new OggExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
ret = new MatroskaExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
ret = new MPEG2TSExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {
// Return now. WVExtractor should not have the DrmFlag set in the block below.
return new WVMExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
ret = new AACExtractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {
ret = new MPEG2PSExtractor(source);
} if (ret != NULL) {
if (isDrm) {
ret->setDrmFlag(true);
} else {
ret->setDrmFlag(false);
}
} return ret;
}

针对mimetype为空的情况

DataSource.cpp (av\media\libstagefright)

bool DataSource::sniff(
String8 *mimeType, float *confidence, sp<AMessage> *meta) {
*mimeType = "";//mimetype
*confidence = 0.0f;//可信度
meta->clear(); {
Mutex::Autolock autoLock(gSnifferMutex);
if (!gSniffersRegistered) {//如果没有嗅探器直接return
return false;
}
} 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;
}

到这里对应的mimetype和extractor都明确了,再回到StagefrightMediaScanner.cpp中processFileInternal函数,执行完了setDataSouce,执行extractMetadata,依然是跟到StagefrightMetadataRetriver.cpp中

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 mMetaData.valueAt(index).string();//如果解析过就直接放回对应的内容即可
}

再看下parseMetaData函数,这个函数比较长,主要看下一部分

    static const Map kMap[] = {
{ kKeyMIMEType, METADATA_KEY_MIMETYPE, NULL },
{ kKeyCDTrackNumber, METADATA_KEY_CD_TRACK_NUMBER, "tracknumber" },
{ kKeyDiscNumber, METADATA_KEY_DISC_NUMBER, "discnumber" },
{ kKeyAlbum, METADATA_KEY_ALBUM, "album" },
{ kKeyArtist, METADATA_KEY_ARTIST, "artist" },
{ kKeyAlbumArtist, METADATA_KEY_ALBUMARTIST, "albumartist" },
{ kKeyAuthor, METADATA_KEY_AUTHOR, NULL },
{ kKeyComposer, METADATA_KEY_COMPOSER, "composer" },
{ kKeyDate, METADATA_KEY_DATE, NULL },
{ kKeyGenre, METADATA_KEY_GENRE, "genre" },
{ kKeyTitle, METADATA_KEY_TITLE, "title" },
{ kKeyYear, METADATA_KEY_YEAR, "year" },
{ kKeyWriter, METADATA_KEY_WRITER, "writer" },
{ kKeyCompilation, METADATA_KEY_COMPILATION, "compilation" },
{ kKeyLocation, METADATA_KEY_LOCATION, NULL },
}; static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
//创建字符集探测器,这个内容是L版本新加的内容
CharacterEncodingDetector *detector = new CharacterEncodingDetector(); for (size_t i = 0; i < kNumMapEntries; ++i) {
const char *value;
if (meta->findCString(kMap[i].from, &value)) {
if (kMap[i].name) {
// add to charset detector
detector->addTag(kMap[i].name, value);//加入探测器
} else {
// directly add to output list
mMetaData.add(kMap[i].to, String8(value));
}
}
} detector->detectAndConvert();//探测器中执行字符集的探测和转换
int size = detector->size();
if (size) {
for (int i = 0; i < size; i++) {
const char *name;
const char *value;
detector->getTag(i, &name, &value);
for (size_t j = 0; j < kNumMapEntries; ++j) {
if (kMap[j].name && !strcmp(kMap[j].name, name)) {
mMetaData.add(kMap[j].to, String8(value));//最后的数据保存
}
}
}
}

此函数中其他的内容也是解析一些其他的元数据报保存下来。

再回到processFileInternal中,在解析完元数据之后

    for (size_t i = 0; i < kNumEntries; ++i) {
const char *value;
if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) != NULL) {
status = client.addStringTag(kKeyMap[i].tag, value);
if (status != OK) {
return MEDIA_SCAN_RESULT_ERROR;
}
}
}

将数据一个一个通过client的addStringTag往上传递,跟下此过程

MediaScannerClient.cpp (av\media\libmedia)

status_t MediaScannerClient::addStringTag(const char* name, const char* value)
{
handleStringTag(name, value);
return OK;
}

最后会回到MediaScanner.java中的handleStringTag()

        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;")
|| name.equalsIgnoreCase("band") || name.startsWith("band;")) {
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 (mProcessGenres &&
(name.equalsIgnoreCase("genre") || name.startsWith("genre;"))) {
mGenre = getGenreName(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);
} else if (name.equalsIgnoreCase("isdrm")) {
mIsDrm = (parseSubstring(value, 0, 0) == 1);
} else if (name.equalsIgnoreCase("width")) {
mWidth = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("height")) {
mHeight = parseSubstring(value, 0, 0);
} else {
//Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")");
}
}

对应的数据已经被传递到了Java侧,剩下的就比较好处理了

本文只是跟了一条大致的代码流程,先到此,后续有可能还要更新一下。

本文中代码使用的是Android5.1原始代码,欢迎大家留言交流。

版权声明:本文为博主原创文章,未经博主允许不得转载。

<Android Framework 之路>Android5.1 MediaScanner的更多相关文章

  1. <Android Framework 之路>Android5.1 Camera Framework(三)

    上一次讲解了一下startPreview过程,主要是为了画出一条大致的从上到下的线条,今天我们看一下Camera在Framework的sendCommand和dataCallback,这部分属于衔接过 ...

  2. <Android Framework 之路>Android5.1 Camera Framework(一)

    Android5.0 Camera Framework 简介 CameraService启动 CameraService是在MediaServer启动过程中进行的 main_mediaserver.c ...

  3. <Android Framework 之路>Android5.1 Camera Framework(四)——框架总结

    前言 从之前的几篇文件,可以基本弄清楚 Camera从APK,经过framework的衔接,与HAL层进行交互,最终通过驱动完成Camera的一些动作. Camera层次分析 APP层 Framewo ...

  4. <Android Framework 之路>Android5.1 Camera Framework(二)

    上一次讲解了一下CameraService的启动过程,今天梳理一下Camera预览的过程 StartPreview过程 首先,我们还是从应用层的使用入手 Camera.java (packages\a ...

  5. <Android Framework 之路>BootAnimation(1)

    介绍 开机动画,BootAnimation,就是Android手机开机郭晨各种以一个展示给用户的界面,实际是一个多个帧组成的动画,在界面上进行一帧一帧的播放,形成开机动画的效果. 本文针对Androi ...

  6. <Android Framework 之路> N版本 Framework Camera的一些改动

    前言 Android N版本最近发布,Nougat是否好吃,不得而知,慢慢看下~ 感谢AndroidXref这个网站,给开发者提供了大量的便捷~以后学习Android就靠它了. N版本上Framewo ...

  7. <Android Framework 之路>BootAnimation(2)

    前言 上一篇主要讲解了BootAnimation是从何而来,如何启动,从开机,到SurfaceFlinger服务起来,然后到执行开机动画,如果要深入的看里面的代码,是需要花一定的时间的,我们旨在了解大 ...

  8. <Android Framework 之路>多线程

    多线程编程 JAVA多线程方式 1. 继承Thread线程,实现run方法 2. 实现Runnable接口 JAVA单继承性,当我们想将一个已经继承了其他类的子类放到Thread中时,单继承的局限就体 ...

  9. Android Framework 简介

    Android Framework 简介 简介 之前的研究太偏向应用层功能实现了,很多原理不了解没有详记,结果被很多公司技术人员鄙视了,为了减少自己的短板,重新复习了一遍C++.java.Androi ...

随机推荐

  1. 洛谷P1478 陶陶摘苹果(升级版)【水题】

    又是一年秋季时,陶陶家的苹果树结了n个果子.陶陶又跑去摘苹果,这次她有一个a公分的椅子.当他手够不着时,他会站到椅子上再试试. 这次与NOIp2005普及组第一题不同的是:陶陶之前搬凳子,力气只剩下s ...

  2. SOA架构设计的案例分析

    面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台. ...

  3. CSS font-style中italic和Oblique有何区别 标签: css字体 2017-01-05 14:42 60人阅读 评论

    *要搞清楚这个问题,首先要明白字体是怎么回事.一种字体有粗体.斜体.下划线.删除线等诸多属性. 但是并不是所有字体都做了这些,一些不常用的字体,或许就只有个正常体,如果你用Italic,就没有效果了~ ...

  4. 手写DAO框架(四)-SQL执行

    -------前篇:手写DAO框架(三)-数据库连接--------- 前言 通过上一篇写的方法,可以灵活的获取.释放数据库连接,拿到连接之后,我们就可以执行sql了!所以,本篇介绍的就是SQL执行器 ...

  5. Travel Card

    Travel Card time limit per test 2 seconds memory limit per test 256 megabytes input standard input o ...

  6. hdu 1250 简单大整数加法

    #include<stdio.h> #include<string.h> #define N 3100 int a[N],b[N],c[N],d[N],e[N]; int ma ...

  7. redis--周边知识点

    一般Redis服务器内存超过128G内存的机器是非常少的 很少在redis中缓存视频,除非是快播,一般都是缓存文本字段 redis可视化的工具和SQL的管理工具是不一样的,最好是使用REDIS的she ...

  8. HDU 4513 manacher

    Manacher算法,相当于求回文串. 关于Manacher,转 http://blog.sina.com.cn/s/blog_70811e1a01014esn.html 现在进入正题:首先,在字符串 ...

  9. CodeForces 19D Points(离散化+线段树+单点更新)

    题目链接: huangjing 题意:给了三种操作 1:add(x,y)将这个点增加二维坐标系 2:remove(x,y)将这个点从二维坐标系移除. 3:find(x,y)就是找到在(x,y)右上方的 ...

  10. [Wikioi 1226]倒水问题

    题目描写叙述 Description 有两个无刻度标志的水壶.分别可装 x 升和 y 升 ( x,y 为整数且均不大于 100 )的水. 设另有一水 缸,可用来向水壶灌水或接从水壶中倒出的水, 两水壶 ...