<Android Framework 之路>Android5.1 MediaScanner

摘要:
前言MediaScanner是Android系统中媒体文件的扫描过程。它扫描存储空间中的媒体文件并将其存储在数据库中,然后提供通过MediaProvider使用的接口。它在Android多媒体=null&&path中扮演着重要角色。startsWith){scanFile;}}}}在onReceiver方法中,将执行scan()或scanFile()方法。一个用于整个卷,另一个用于特定文件。从传入的参数中,我们可以看到卷名由scan传入,文件路径由scanFile传入。通过接收广播开始扫描。但是,scan和scanFile都以startService的方式启动MediaScannerService。

前言

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 

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇List集合, 时间正序排列、倒序排序Linux网络编程——进程池实现过程详解(2)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[转]Android 操作SQLite基本用法

在Android开发中SQLite起着很重要的作用,网上SQLite的教程有很多很多,不过那些教程大多数都讲得不是很全面。本人总结了一些SQLite的常用的方法,借着论坛的大赛,跟大家分享分享的。一.SQLite的介绍1.SQLite简介SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入  式的,而且目前已经在很多嵌入...

AutoCAD ObjectARX和RealDWG的基本数据操作

.dwg文件的读取。拥有RealDWG或者ObjectARX不必多说。 安装RealDWG后RealDWG目录下的Samples看看。 新建工程配置好Inc,Lib,附加依赖等。 不必多说。 需要将AcDbHostApplicationServices实现一遍。 sample自带的即可,也可自己按照自己的来写。 1 int Verbose = 0...

云计算设计模式(八)——外部配置存储模式

云计算设计模式(八)——外部配置存储模式 移动配置信息从应用部署包到一个集中位置。这个模式可以提供机会,以便管理和配置数据的控制,以及用于跨应用程序和应用程序实例共享的配置数据。  背景和问题 大多数应用程序运行时环境包括位于应用程序文件夹内的在部署应用程序文件保持配置信息。在某些情况下也能够编辑这些文件来改变该应用程序的行为,它已经被部署之后。然而,在...

springboot后端实现条件查询,要配合使用mybatis

packagecn.com.dyg.work.sqlgen; importcn.com.dyg.work.common.exception.DefException; importcn.com.dyg.work.common.utils.CamelAndUnderLineConverter; importcom.alibaba.fastjson.JSON...

【Android Developers Training】 101. 显示快速联系人挂件(Quick Contact Badge)

注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。 原文链接:http://developer.android.com/training/contacts-provider/display-contact-badge.html 这节课将会向你展示如何添...

hiveql函数笔记(二)

1、数据查询 //提高聚合的性能 SET hive.map.aggr=true; SELECT count(*),avg(salary) FROM employees; //木匾不允许在一个查询语句中使用多于一个的函数(DISTINCT。。。)表达式 SELECT count(DISTINCT symbol) FROM stocks; 表生成函数: exp...