Android多媒體
通過調(diào)用Android的API,可以實(shí)現(xiàn)相冊,播放器,錄音和攝像等功能。這一章需要掌握如下功能:
q 多媒體的ContentProvider的調(diào)用
q Camera
q AudioRecord和AudioTrack
q MediaPlayer
11.1 獲取多媒體信息
多媒體信息?在pc中的音樂播放器總是很容易的顯示歌手名、歌曲名、專輯名、年代。在Android中應(yīng)該如何獲取這些信息呢?
11.1.1 查看多媒體ContentProvider
前面我們學(xué)習(xí)了ContentProvider來保存和檢索數(shù)據(jù),Android為常用的數(shù)據(jù)類型(如:音視頻、圖片和聯(lián)系方式等)提供了大量的ContentProvider,它們被定義在android.provider包下。那么我們?nèi)绾潍@取多媒體的ContentProvider呢。
(1) 在Eclipse中添加Android自帶的FileExplorer視圖:
菜單欄->window->show view->other->Android->FileExplorer
(2) 開啟模擬器,在FileExplorer中查看data/data/com.android.providers.media/databases/(如圖11.1)
(3) 將external.db文件 pull到pc上,用sqlite工具(可以使用火狐插件SQliteManager)查看:(如圖11.2)
(4) 查看表結(jié)構(gòu):
audio_meta:管理sd卡中的音頻資源。(如圖11.3)
video:管理sd卡中的視頻資源。(如圖11.4)
images:管理sd卡中的圖片。(如圖11.5)
圖11.1 多媒體數(shù)據(jù)庫所在包

圖11.2 多媒體數(shù)據(jù)庫表

圖11.3 音頻表結(jié)構(gòu)(audio_meta)

圖11.4 視頻表結(jié)構(gòu)(video)

圖11.5 圖片表結(jié)構(gòu)(images)

可能大家看到這里會(huì)不明白為什么講多媒體先要介紹這些。筆者這里給大家列舉一個(gè)音樂播放器的需求:
1) 獲得音樂文件列表及路徑
2) 獲得音樂文件屬性(歌曲名、歌手名、專輯、年代。。)
3) 手動(dòng)刪除內(nèi)存卡中音樂文件,如何能同步更新文件列表
如果是沒有Android經(jīng)驗(yàn),大家可能會(huì)這樣分析:
文件路徑嘛,我調(diào)用file.listFile()就可以得到音樂文件列表。如果內(nèi)存卡里有很多文件,那么這個(gè)將會(huì)特別的耗時(shí),如果讓用戶等上10秒去掃描存儲(chǔ)卡,用戶很有可能將你的應(yīng)用卸載掉。
音樂文件屬性一般保存在音樂文件中,有的放在文件頭,有的放在文件尾,必須讀出該文件相關(guān)字節(jié)中的內(nèi)容才可以獲取音樂文件信息。OK,有個(gè)開源的項(xiàng)目,可以解析MP3文件中的文件信息。但是它同樣也是耗時(shí)的操作。而且學(xué)會(huì)調(diào)用這個(gè)開源項(xiàng)目還需要一周的時(shí)間。做一個(gè)項(xiàng)目沒那么難吧?
在文件管理器中手動(dòng)刪除或添加一個(gè)音樂文件,這個(gè)時(shí)候如何把它更新到音樂列表中呢,當(dāng)你選擇一首歌播放的時(shí)候,很有可能已經(jīng)被刪掉了??偛荒茏屗鼤r(shí)刻去調(diào)用file.listFile()去維護(hù)吧?
那么我們到底應(yīng)該怎么做呢?你要始終相信Android是強(qiáng)大的,它早已為我們提供了這些功能,我們只需要調(diào)用Android提供的API,就可以解決上述的需求。
現(xiàn)在我們先看個(gè)圖,圖11.6為模擬器開機(jī)時(shí),在log中打印的日志。
圖11.6 開機(jī)時(shí)的log

大家會(huì)看到,系統(tǒng)會(huì)調(diào)用MediaScanner去掃描Internal和External Volume。原來,在開機(jī)時(shí),系統(tǒng)會(huì)在后臺(tái)掃描內(nèi)存和外存設(shè)備,將多媒體數(shù)據(jù)更新到數(shù)據(jù)庫中。同時(shí)也會(huì)掃描文件信息,這樣,我們不費(fèi)吹灰之力就解決了問題。
小插曲:之前由于筆者還不知道這個(gè)方法,在摸索中讓同事去研究解析MP3文件信息,花了他一個(gè)星期時(shí)間,當(dāng)他做出來了,我花了十分鐘找到了這個(gè)方法。真是罪過罪過。
那么我們?nèi)绾蔚玫蕉嗝襟w數(shù)據(jù)呢,請看下面的例子:
/**
* 讀取sd卡中的音樂文件
* @return
* @throws Exception
*/
public static ArrayList<Song> readDataFromSD(Context context,int component){
// Log.i(TAG, "scanFile");
Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.YEAR, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Media.SIZE, MediaStore.Audio.Media.DATA}
, MediaStore.Audio.Media.MIME_TYPE+"=? or " + MediaStore.Audio.Media.MIME_TYPE+"=?", new String[]{"audio/mpeg","audio/x-ms-wma"},null);
ArrayList<Song> songs = null;
if (cursor.moveToFirst()) {
songs = SongInfoUtil.getList(cursor, context, component);
}
return songs;
}
/**
* @author stayzhang 封裝信息至Song
*/
public class SongInfoUtil {
// private static final String TAG = "ListUtils";
public static ArrayList<Song> getList(Cursor cursor, Context mContext, int component) {
Song song = null;
do {
// Log.i(TAG, "getList");
song = new Song();
song.setFilename(cursor.getString(1));//文件名
song.setTitle(cursor.getString(2));//歌曲名
song.setDuration(cursor.getInt(3));//時(shí)長
song.setSinger(cursor.getString(4));//歌手名
song.setAlbum(cursor.getString(5));//專輯名
if (cursor.getString(6) != null) {//年代
song.setYear(cursor.getString(6));
} else {
song.setYear("未知");
}
if ("audio/mpeg".equals(cursor.getString(7).trim())) {//歌曲格式
song.setType("mp3");
} else if ("audio/x-ms-wma".equals(cursor.getString(7).trim())) {
song.setType("wma");
}
if (cursor.getString(8) != null) {//文件大小
float temp = cursor.getInt(8) / 1024f / 1024f;
String sizeStr = (temp + "").substring(0, 4);
song.setSize(sizeStr + "M");
} else {
song.setSize("未知");
}
if (cursor.getString(9) != null) {//文件路徑
song.setFileUrl(cursor.getString(9));
}
} while (cursor.moveToNext());
cursor.close();
return dbService.query(component, null, null);
}
}
同理,圖片還有視頻文件也可以這樣獲得,不再贅述。
這樣第一個(gè)和第二個(gè)需求我們已經(jīng)解決,那第三個(gè)需求呢。
當(dāng)我們手動(dòng)的刪除或添加多媒體文件到存儲(chǔ)卡中時(shí),Android會(huì)自動(dòng)掃描這些文件并將其更新到數(shù)據(jù)庫嗎?
答案是不會(huì),那么我們?nèi)绾螌?shù)據(jù)實(shí)時(shí)更新到數(shù)據(jù)庫中呢。還記得logcat中打印出來的MediaScanner嗎?我們也可以調(diào)用MediaScanner這個(gè)類去掃描存儲(chǔ)卡。但是不能直接調(diào)用這個(gè)類,只能以廣播的形式通知系統(tǒng),讓系統(tǒng)去掃描存儲(chǔ)卡指定的URI,掃描完后,再通過ContentProvider查詢數(shù)據(jù)庫。
方法如下:
/**
* 調(diào)用系統(tǒng)api掃描sd卡
*/
private void scanSdCard() {
IntentFilter intentFilter = new IntentFilter(
Intent.ACTION_MEDIA_SCANNER_STARTED);
intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
intentFilter.addDataScheme("file");
scanReceiver = new ScanSdFilesReceiver();
registerReceiver(scanReceiver, intentFilter);
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + getExternalStorageDirectory().getAbsolutePath)));
}
/**
* @author stayzhang 注冊掃描開始/完成的廣播
*/
private class ScanSdFilesReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_MEDIA_SCANNER_STARTED.equals(action)) {
//當(dāng)系統(tǒng)開始掃描sd卡時(shí),為了用戶體驗(yàn),可以加上一個(gè)等待框
scanHandler.sendEmptyMessage(STARTED);
}
if (Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
//當(dāng)系統(tǒng)掃描完畢時(shí),停止顯示等待框,并重新查詢ContentProvider
scanHandler.sendEmptyMessage(FINISHED);
}
}
}
OK,關(guān)于多媒體的ContentProvider的知識(shí)告一段落,下面就是如何調(diào)用Android API來使用這些多媒體文件。