MediaScanner原始碼簡單分析

易冬發表於2019-03-04

前言

MediaScanner是Android系統中針對媒體檔案的掃描過程,將儲存空間中的媒體檔案通過掃描的方式遍歷並儲存在資料庫中,然後通過MediaProvider提供介面使用,在Android多媒體中佔有很重要的位置。

原始碼位置

packagesprovidersmediaprovider
frameworksasemediajavaandroidmedia
frameworksavmedialibmedia
frameworksasemediajni
frameworksavmedialibstagefright

掃描過程

MediaScannerReceiver.java (packagesprovidersmediaprovidersrccomandroidprovidersmedia)
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 (packagesprovidersmediaprovidersrccomandroidprovidersmedia)
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 (basemediajavaandroidmedia)

  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
");
                Log.d(TAG, "    scan time: " + (scan - prescan) + "ms
");
                Log.d(TAG, "postscan time: " + (end - scan) + "ms
");
                Log.d(TAG, "   total time: " + (end - start) + "ms
");
            }
        } 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 (basemediajni)

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 (avincludemediastagefright)

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 (avmedialibmediaplayerservice)

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 (avmedialibstagefright)
那麼然後執行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 (avmedialibstagefright)

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 (avmedialibstagefright)

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 (avmedialibmedia)

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  01 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側,剩下的就比較好處理了
本文只是跟了一條大致的程式碼流程,先到此,後續有可能還要更新一下。

z

本文中程式碼使用的是Android5.1原始程式碼,歡迎大家留言交流。
版權宣告:本文為博主原創文章,未經博主允許不得轉載。

相關文章