zoukankan      html  css  js  c++  java
  • <Android Framework 之路>Android5.1 MediaScanner

    前言

    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侧,剩下的就比较好处理了
    本文只是跟了一条大致的代码流程,先到此,后续有可能还要更新一下。

    本文中代码使用的是Android5.1原始代码,欢迎大家留言交流。
    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    静态网页
    css
    html
    数据分析器
    初步了解计算机
    如何导出数据库的数据词典
    阅读计划
    python之文件读写
    曾梦想仗剑走天涯,看世界的繁华
    python lambda匿名函数
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6467215.html
Copyright © 2011-2022 走看看