1. 程式人生 > >Android多媒體分析(一)MediaScanner

Android多媒體分析(一)MediaScanner

Android平臺上的媒體檔案管理和桌面系統不同。在桌面系統上,不同目錄下的媒體檔案呈樹狀結構顯示給使用者,使用者需要進入不同目錄尋找該目錄下的檔案。而在Android平臺上,不同目錄下的媒體檔案則以一層列表方式顯示給使用者,使用者不需進入子目錄就可以列出(某種型別的)所有媒體檔案。

在Android上,為了實現這種模式的媒體檔案管理,對所有管理的媒體檔案抽取其元資料,也就是ID3(mp3檔案包含的元資料可參考http://en.wikipedia.org/wiki/ID3),儲存在資料庫中,並作為一個content provider提供給其他應用使用。使用者的每一次顯示媒體檔案的操作,就是對這個資料庫的一次查詢操作。在多媒體管理模組中,主要分成三個模組:

多媒體資料庫

MediaStore這個類是android系統提供的一個多媒體資料庫,android中多媒體資訊都可以從這裡提取。這個MediaStore包括了多媒體資料庫的所有資訊,包括音訊,視訊和影象,android把所有的多媒體資料庫介面進行了封裝,所有的資料庫不用自己進行建立,直接呼叫利用ContentResolver去掉用那些封裝好的介面就可以進行資料庫的操作,多媒體資料庫的使用方法和SQLITE3的方法是一樣的。

MediaStore中的資料是在MediaScanner掃描後通過MediaProvider中的一個service進行更新的。框架圖如下:

MediaScanner

在Android系統中,多媒體庫是通過MediaScanner去掃描磁碟檔案,對元資訊的處理,並通過MediaProvider儲存到MediaStore中。下圖為MediaScannerr 框架:

             圖1-1  MediaScanner框架流程

MediaScanner可以通過手動控制,在ANDROID系統中,已經定製了三種事件會觸發MediaScanner去掃描磁碟檔案:ACTION_BOOT_COMPLETED、ACTION_MEDIA_MOUNTED、 ACTION_MEDIA_SCANNER_SCAN_FILE。其中ACTION_BOOT_COMPLETED是系統啟動完後發出這個訊息,ACTION_MEDIA_MOUNTED是插卡事件觸發的訊息,ACTION_MEDIA_SCANNER_SCAN_FILE訊息一般是在一些檔案操作後,開發人員手動發出的一個重新掃描多媒體檔案的訊息。傳送訊息通過sendBroadcast函式完成,比如廣播一個ACTION_MEDIA_MOUNTED訊息:

sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"

                       + Environment.getExternalStorageDirectory())));

     由上可知是通過傳送了一個廣播(傳遞對應的掃描要求)來觸發重新掃描磁碟事件,那麼可以猜測系統肯定存在一個廣播接收器(何時何地註冊?),在收到這個廣播訊息後,通過對應引數啟動MediaScannerService。MediaScannerService呼叫一個公用類MediaScanner去處理真正的工作。MediaScannerReceiver維持兩種掃描目錄:一種是內部卷(internal volume)指向$(ANDROID_ROOT)/media. 另一種是外部卷(external volume)指向$(EXTERNAL_STORAGE),掃描的位置可以修改(一般外部不用修改,預設為SDCARD,內部根據驅動命名的INAND路經名做對應的修改),對應圖1-1系統原始碼具體位置:

MediaScannerReceiver: /mydroid/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerReceiver.java

Scanner事件接收,繼承了BroadcastReceiver類,收到掃描訊息後啟動MediaScannerService,但有一點要注意的是: Service的onCreate的方法只會被呼叫一次,就是你無論多少次的startService又 bindService,Service只被建立一次。如果先是bind了,那麼start的時候就直接執行Service的 onStart方法,如果先是start,那麼bind的時候就直接執行onBind方法。如果你先bind上了,就stop不掉了,對啊,就是stopService不好使了,只能先UnbindService, 再StopService,所以是先start還是先bind行為是有區別的。(關於BINDLE介面請參考其它文件)

MediaScannerService:

/mydroid/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java

通過此服務,去呼叫MediaScanner的具體實現方法。

MediaScanner:  (方法)

\frameworks\base\media\java\android\media\ MediaScanner.java

MediaPlayer整體流程:

Thumbnails縮圖概述

縮圖儲存位置:/sdcard/DCIM/.thumbnails

縮圖(大):一張張的JPEG檔案

所有的都存入同一個二進位制檔案.thumbdata+version+”-”+hashcode,位元組大小均為 10000bytes

詳解:

對於每一張圖片,都會生成大縮圖和小縮圖,大縮圖儲存在(外部裝置)/sdcard/DCIM/.thumbnails/ 目錄下,大縮圖大小上限是512 X 384,下限1 X1。小縮圖被統一儲存到一個隨機訪問檔案 (外部裝置)/sdcard/DCIM/.thumbnails/ +".thumbdata" + version + "-"+ mUri.hashcode()

縮圖儲存到資料庫裡面

掃描一個檔案的最後endfile()會mMediaProvider.insert()或者mMediaProvider.update()。在mMediaProvider.insert()時對於IMAGES_MEDIA 和VIDEO_MEDIA型別:requestMediaThumbnail()--->mCurrentThumbRequest.execute(); 在執行時會首先會去縮圖的資料庫中查詢,查詢到就返回,未查詢到會ThumbnailUtil.createVideoThumbnail(mPath)或者 ThumbnailUtil.createImageThumbnail(mCr, mPath, mUri, mOrigId,Images.Thumbnails.MICRO_KIND, true/*saveMini*/));

建立圖片縮圖:

建立thumbnail時呼叫ThumbnailUtil.makeBitmap()建立;如果saveMini為真會儲存縮圖ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(), bitmap.getHeight());; 儲存時會通過origId向資料庫查詢,查到返回對應的uri,未查詢到就插入資料庫並返回uri,最終返回bitmap。對於建立視訊縮圖:直接在視訊檔案中取一幀得到bintmap並返回儲存大縮圖到檔案: bitmap.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);儲存小縮圖到隨機訪問檔案:data = miniOutStream.toByteArray(); miniThumbFile.saveMiniThumbToFile(data, mOrigId, magic);

在mMediaProvider.update()時,對IMAGES_MEDIA,IMAGES_MEDIA_ID,VIDEO_MEDIA,VIDEO_MEDIA_ID型別會查詢magic,如果沒查到會執行requestMediaThumbnail(),流程同上。

MediaProvider是對上面整個流程的實現。可參考裡面的程式碼。

圖3 MediaProvider 程式碼架構

Android稍有熟悉的人都知道,Android Media Scanner只對SD卡上的媒體檔案進行掃描。其掃描的策略,請參考《Android Media Scanner Process》。假如我們的硬體平臺上面沒有提供SD卡槽,難道Android就不能進行對媒體檔案播放了嗎?當然不是的,否則Android系統將不會成為一個完善的Framework。本文結合本人的經驗介紹一下,怎樣修改多媒體檔案的掃描路徑。

     根據《Android Media Scanner Process》的介紹我們可以知道,Android scanner掃描媒體完成之後,會把媒體檔案存放在資料庫中,由MediaProvider為上層的應用程式提供服務。

    經過研究Media scanner的程式碼發現,他的掃描路徑為android.os.Environment.EXTERNAL_STORAGE_DIRECTORY。定義該變數檔案位於:

frameworks/base/core/java/android/os/Environment.java

預設情況下,Android將會搜尋/sddisk目錄:

    private static final File EXTERNAL_STORAGE_DIRECTORY

            = getDirectory("EXTERNAL_STORAGE", "/sddisk");

為了讓其進行搜尋我們自定義的路徑,可以修改該變數的定義,加入我們希望掃描/external目錄。程式碼修改如下:

    private static final File EXTERNAL_STORAGE_DIRECTORY

            = getDirectory("EXTERNAL_STORAGE", "/external");

這樣Android Media Scanner將會搜尋/external目錄來查詢媒體檔案。

     下一步我們需要保證這個檔案一定要存在,我們需要修改init.rc檔案。增加如下的定義:

mkdir /external 0777 system system

這樣在開機的時候,如果/external目錄不存在,則會建立一個。如果已經存在,則不會有任何動作。

     另外怎樣觸發Media Scanner?根據《Android Media Scanner Process》的介紹,當收到ACTION_BOOT_COMPLETEDACTION_MEDIA_MOUNTEDACTION_MEDIA_SCANNER_SCAN_FILE訊息的時候才會進行掃描。以前是掃描SD卡,當SDmount的時候Android系統會有ACTION_MEDIA_MOUNTED訊息通知,Media Scanner開始掃描媒體檔案。但是我們的/external目錄修改之後,怎樣通知Android media scanner掃描呢?一個辦法是重啟,沒有人樂意這樣做。另外一個辦法是執行menu->dev tools->Media Scan,這樣將會進行掃描。目前我還沒有讓目錄修改之後,自動掃描的辦法。如果你有好的點子,請你給我留言。

     通過以上的步驟,可以在Android/external目錄存放媒體檔案,並且被music應用程式播放了。