Android MediaProvider,MediaScanner媒体文件扫描源码解析

关于媒体文件扫描,我们需要弄明白两个问题:
1.什么时候开启媒体文件扫描
2.如何解析媒体文件(音频,视频,图片)信息插入到数据库中,对应code流程

Android MediaProvider,MediaScanner媒体文件扫描源码解析_第1张图片

我们现在音乐,视频播放器,图库等应用关于音视频图片等信息都是通过多媒体数据库直接查询
在源码 packages\providers\下有个叫MediaProvider(进程名:android.process.media)
它主要处理管理这个媒体数据库,处理MTP,铃声选择也在当中.

在MediaProvider中有一个广播接收类MediaScannerReceiver,当中监听以下几个广播:


    "android.intent.action.BOOT_COMPLETED" /> //开机完成
    "android.intent.action.MEDIA_MOUNTED" />   //挂载 
    "android.intent.action.MEDIA_UNMOUNTED" />   //卸载 
    "android.intent.action.MEDIA_SCANNER_SCAN_FILE" /> //扫描文件
    "android.intent.action.ACTION_SHUTDOWN" /> //关机
     --------------------------------------------------------------------------------
      //以下两个是我司内部加的
    "android.intent.action.ACTION_SHUTDOWN_IPO" /> 
    "android.intent.action.OVERTIME_REMOVAL" /> // 热插拔优化广播


从源码看,会启动扫描动作的是BOOT_COMPLETED(开机),MEDIA_MOUNTED(挂载)
MEDIA_UNMOUNTED(卸载),MEDIA_SCANNER_SCAN_FILE(扫描文件)会执行扫描.

在开机后会执行

scan(context, MediaProvider.INTERNAL_VOLUME); //扫描内部存储
scanUntilAllStorageMounted(context);//挂载大于5秒或者挂载成功后执行 
scan(context, MediaProvider.EXTERNAL_VOLUME);//扫描外部存储

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)); }

我们可以看到当调用scan(),scanFile()方法,会启动MediaScannerService服务执行扫描
该类继承Service实现Runnable

总结:
MediaScannerReceiver是用来接收任务,它收到广播后,会启动MediaSannerService进行扫描工作。

在MediaScannerService onCreate中开启了线程,Service是跑在主线程的,扫描是个耗时的操作,避免堵塞开启线程

public class MediaScannerService extends Service implements Runnable
Thread thr = new Thread(null, this, "MediaScannerService");thr.start();
//在线程run()中拿到线程消息队列,初始化handler
@Override
public void run() {  
  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +            Process.THREAD_PRIORITY_LESS_FAVORABLE);   
   Looper.prepare();
   mServiceLooper = Looper.myLooper();   
   mServiceHandler = new ServiceHandler();                                                                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +  Process.THREAD_PRIORITY_LESS_FAVORABLE);
        Looper.loop();}

在onStartCommand中会实际执行扫描调用

//在onStartCommand中会实际执行扫描调用
if (arguments.getString(“filepath”) != null) { 
//文件扫描    
what = MSG_SCAN_SINGLE_FILE;    
Message msg = mServiceHandler.obtainMessage(what, startId, -1, arguments);    mServiceHandler.sendMessage(msg);
} else { 
//存储扫描     // Cancel un-finished task    
synchronized (MediaScannerService.this) {     
  ………………………………..       
mPrescanTaskList.remove(cancelList);        
PrescanTask prescanTask = new PrescanTask(arguments,mServiceHandler,startId);        mPrescanTaskList.add(prescanTask);        
prescanTask.execute();
//使用AsyncTask    
}}

在PrescanTask doInBackground中执行预扫描的操作,在onPostExecute中会发送message执行目录扫描操作
ServiceHandler处理四种message
1.文件路径扫描 handleScanSingleFile(msg);
2.目录扫描 handleScanDirectory(msg);
3.关闭扫描线程池 handleShutdownThreadpool();
4.扫描完成 handleScanFinish();

在PrescanTask doInBackground中执行预扫描的操作,在onPostExecute中会发送message执行目录扫描操作
ServiceHandler处理四种message
1.文件路径扫描 handleScanSingleFile(msg);
2.目录扫描 handleScanDirectory(msg);
3.关闭扫描线程池 handleShutdownThreadpool();
4.扫描完成 handleScanFinish();

让我们看看目录扫描代码

private void handleScanDirectory(Message msg) {    
Bundle arguments = (Bundle) msg.obj;    
try {       
……        
if (MediaProvider.INTERNAL_VOLUME.equals(volume)) 
{             
// scan internal media storage            
directories = new String[] {            
 Environment.getRootDirectory() + "/media",
 };       
  // scan external storage volumes           
 /// M: MediaScanner Performance turning {@    
  /// Thread pool enable, use threadpool to scan. 
 //支持线程池 使用线程池扫描
if (mIsThreadPoolEnable) {             
   mStartId = msg.arg1;              
 }                
 }           
  /// @}        
  }  

       if (directories != null) {            
       long start = System.currentTimeMillis();            
       scan(directories, volume);
        long end = System.currentTimeMillis();            ...............................................     
           }
               } catch (Exception e) {        
 /// M: MediaScanner Performance turning {@    /// Only stop service when thread pool terminate   
stopSelfResult(mStartId);        
mStartId = msg.arg1;    }
 else {       
 }    /// @}
 }

1.使用线程池调用scanWithThreadPool()
2.不使用线程池最终都是通过MediaScanner进行扫描处理

1.使用线程池调用scanWithThreadPool()
2.不使用线程池最终都是通过MediaScanner进行扫描处理

private void scan(String[] directories, String volumeName) {   
 Uri uri = Uri.parse("file://" + directories[0]);   
  // don't sleep while scanning
      mWakeLock.acquire();
          try {        ………..
                  sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
            //发送开始扫描广播        
            try {         ……….
                        try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
                                        scanner.scanDirectories(directories); 
                      //最终扫描由MediaScanner中的 scanDirectories处理            
                      }        } catch (Exception e) {            
                      MtkLog.e(TAG, "exception in MediaScanner.scan()", e);
                              }}

        getContentResolver().delete(scanUri, null, null);    
        } catch (Exception ex) {        
        MtkLog.e(TAG, "exception in MediaScanner.scan()", ex);
            } finally {        
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
            //发送结束扫描广播
                    mWakeLock.release();
                            MtkLog.d(TAG, "scan>>>: volumeName = " + volumeName            + ", directories = " + Arrays.toString(directories));
                                }}

MediaScanner是多媒体库扫描核心类之一,干实事的类,把解析后的数据插入到数据库当中就是在该类中完成
MediaScanner源码路径在 frameworks\base\media\java\android\media\MediaScanner.java
MediaScanner.java 注释如下:

* In summary:
* Java MediaScannerService calls
* Java MediaScanner scanDirectories, which calls
* native MediaScanner processDirectory, which calls
* native MyMediaScannerClient scanFile, which calls
* Java MyMediaScannerClient scanFile, which calls
* Java MediaScanner processFile (native method), which calls
* native MediaScanner processFile, which calls
* native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls
* Java MyMediaScanner handleStringTag.
* Once MediaScanner processFile returns, an entry is inserted in to the database.*
* The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner.*


在MediaScannerService当中最终通过scan()调用到MediaScanner中的scanDirectories()执行扫描


public void scanDirectories(String[] directories) {
    try {        
    long start = System.currentTimeMillis();
            prescan(null, true);//扫描前预处理        
            long prescan = System.currentTimeMillis();
                    if (ENABLE_BULK_INSERTS) {
                                // create MediaInserter for bulk inserts            mMediaInserter = new MediaInserter(mMediaProvider, 500);
                                        }
        for (int i = 0; i < directories.length; i++) {
        processDirectory(directories[i], mClient);
            //JNI调用最终处理在MediaScanner.cpp中processDirectory()处理
if (ENABLE_BULK_INSERTS) {            
// flush remaining inserts            
mMediaInserter.flushAll();            
mMediaInserter = null;       
 }      
  ………………………………………….

processDirectory()的调用最终会回到,MyMediaScannerClient(位置在MediaScaner.java中)的doScanFile
MyMediaScannerClient是MediaScanner中的内部类,所有重要关键的操作都在该类中完成。

public Uri doScanFile(String path, String mimeType, long lastModified,
       long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
       Uri result = null;
       ……
       boolean isaudio = MediaFile.isAudioFileType(mFileType);
       boolean isvideo = MediaFile.isVideoFileType(mFileType);
       boolean isimage = MediaFile.isImageFileType(mFileType);
       ……
        // we only extract metadata for audio and video files
        if (isaudio || isvideo) {

  processFile(path, mimeType, this);
 /* processFile 是一个 native 方法,最终实现在 native层StagefrightMediaScanner.cpp 中
 }
 if (isimage) {
  processImageFile(path);
 }
 result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
//当数据全部解析完成后,通过endFile插入到数据库当中               
……
return result;
 }

在doScanFile()中调用到非常重要的方法processFile,这最终实现是在StagefrightMediaScanner.cpp当中,让我们看看processFile当中做了什么
StagefrightMediaScanner.cpp源码路径frameworks\av\media\libstagefright\StagefrightMediaScanner.cpp


MediaScanResult StagefrightMediaScanner::processFile(        
const char *path, const char *mimeType,        
MediaScannerClient &client) {#ifdef MTK_AOSP_ENHANCEMENT    
ALOGI("processFile '%s'.", path);
#else   
 ALOGV("processFile '%s'.", path);#endif 
 // #ifdef MTK_AOSP_ENHANCEMENT
     client.setLocale(locale());//设置语言    
     client.beginFile();    MediaScanResult result = processFileInternal(path, mimeType, client);
    //在processFileInternal中调用了两个StagefrightMetadataRetriever中重要的两个方法
      setDataSource(),extractMetadata()    client.endFile();    return result;}

调用了StagefrightMetadataRetriever.cpp中的setDataSource(),在该方法中会调用MediaExtractor.cpp的Create创建不同格式的解析器,音视频解析就看这些东东了


status_t StagefrightMetadataRetriever::setDataSource(
        const sp &httpService,       
         const char *uri,
                 const KeyedVector *headers) {
                 #ifdef MTK_AOSP_ENHANCEMENT   
                 ATRACE_CALL();#endif
                            ALOGV("setDataSource(%s)", uri);    
                            clearMetadata();    
                            mSource = DataSource::CreateFromURI(httpService, uri, headers);   …..
#ifdef MTK_AOSP_ENHANCEMENT    
const char * sniffedMIME = NULL;
    if ((!strncasecmp("/system/media/audio/", uri, 20)) && (strcasestr(uri,".ogg") != NULL))    {         
    sniffedMIME = MEDIA_MIMETYPE_CONTAINER_OGG;    }    mExtractor = MediaExtractor::Create(mSource, sniffedMIME);//创建不同的解析器 
    #else    mExtractor = MediaExtractor::Create(mSource);#endif

在extractMetadata()调用到解析器中parseMetaData()解析文件
解析后的信息如何插入到数据库中呢?
在StagefrightMediaScanner的processFileInternal中会把解析到信息通过addStringTag()传回MyMediaScannerClient中的handleStringTag中
看看handleStringTag做了什么

看以下备注

只是一个简单赋值
最后插入数据库由endFile去完成

通过MTP拷贝文件到手机,这个扫描流程是怎样的呢?
PC发SendObjectInfo命令给MtpServer。MtpServer需要检查存储设备剩余空间、可支持的最大文件大小。如果一切正常的话,它会通过MediaProvider的insert函数往媒体数据库中加入一条数据项。
接着PC通过SendObject将文件内容传递给给MtpServer。而MtpServer就会创建该文件,并把数据写到文件中。
当文件数据发送完毕,MtpServer调用endSendObject。而endObject则会触发MediaScanner进行媒体文件扫描。当然,扫描完后,该文件携带的媒体信息(假如是MP3文件的话,则会把专辑信息、歌手、流派、长度等内容)加入到媒体数据库中。
最终通过调用MtpDatabase.java中的endSendObject方法调用MediaScanner的scanMtpFile执行文件扫描,流程跟上面类似

你可能感兴趣的:(Android,OS,源码分析,音视频)