Android多媒体开发
Android多媒體開發(fā)系列文章
一、什么是多媒體
多媒體(duō méi tǐ) 的英文單詞是Multimedia,它由media和multi兩部分組成。一般理解為多種媒體的綜合
多媒體是計(jì)算機(jī)和視頻技術(shù)的結(jié)合,實(shí)際上它是兩個(gè)媒體;聲音和圖像,或者用現(xiàn)在的術(shù)語:音響和電視。
多媒體(Multimedia),在計(jì)算機(jī)系統(tǒng)中,組合兩種或兩種以上媒體的一種人機(jī)交互式信息交流和傳播媒體
使用的媒體包括文字、圖片、照片、聲音 (包含音樂、語音旁白、特殊音效)、動(dòng)畫和影片,以及程式所提供的互動(dòng)功能
多媒體是超媒體(Hypermedia)系統(tǒng)中的一個(gè)子集,而超媒體系統(tǒng)是使用超鏈接 (Hyperlink)構(gòu)成的全球信息系統(tǒng)
全球信息系統(tǒng)是因特網(wǎng)上使用 TCP/IP 協(xié)議和 UDP/IP 協(xié)議
二、音樂播放器
Android 官方提供了MediaPlayer 核心類,用于播放音樂,其狀態(tài)流程如下圖所示。MediaPlayer 必須嚴(yán)格按照狀態(tài)圖操作,否則就會(huì)出現(xiàn)錯(cuò)誤,這些錯(cuò)誤都是底層拋出,嚴(yán)格按照狀態(tài)圖操作的話一般就不會(huì)出問題。
MediaPlayer,原生的API,可以播放音視頻,但是支持的格式比較少,實(shí)際開發(fā)中用的比較少,但是還是很有必要學(xué)習(xí),熟悉API,因?yàn)閂itamio框架的API大部分跟原生的API是一樣的
1、MediaPlayer使用流程圖
2、MediaPlayer核心方法
| create() | 播放本地res/raw/目錄下的資源 |
| reset() | 重置為初始狀態(tài) |
| setAudioStreamType() | 設(shè)置音樂格式,例如:AudioManager.STREAM_MUSIC |
| setDataSource() | 設(shè)置音頻源,本地網(wǎng)絡(luò)資源均可 |
| prepare() | 播放前的準(zhǔn)備工作 |
| prepareAsync() | 異步進(jìn)行準(zhǔn)備工作,播放網(wǎng)絡(luò)音頻的時(shí)候使用 |
| start() | 開始或恢復(fù)播放 |
| pause() | 暫停播放 |
| stop() | 停止播放 |
| release() | 釋放資源 |
| getDuration() | 獲取音樂最大長(zhǎng)度(毫秒單位) |
| getCurrentPosition() | 獲取當(dāng)前的播放進(jìn)度 |
| seekTo() | 拖拽進(jìn)度 |
| setDisplay() | 設(shè)置輸出畫面 |
| setOnPreparedListener() | 設(shè)置準(zhǔn)備監(jiān)聽 |
為了演示MediaPlayer 的使用,我們需要提前準(zhǔn)備一個(gè)mp3 文件放到sdcard 中
需求:制作一個(gè)播放器,能夠播放/暫停/停止音樂文件,并且添加一個(gè)SeekBar(可以拖拽的ProgressBar),當(dāng)音樂播放時(shí)SeekBar 也會(huì)不斷的跟新當(dāng)前的進(jìn)度,當(dāng)用戶拖動(dòng)SeekBar 時(shí)可以更改播放的進(jìn)度
布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><Button android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:onClick="play"android:text="播放"/><Button android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:onClick="pause"android:text="暫停"/><Button android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:onClick="stop"android:text="停止"/></LinearLayout><SeekBar android:id="@+id/sb"android:layout_width="match_parent"android:layout_height="wrap_content"/> </LinearLayout>代碼實(shí)現(xiàn)
public class MainActivity extends Activity implements OnSeekBarChangeListener {private SeekBar sb;private MediaPlayer player;private int duration;// 播放器的幾個(gè)狀態(tài)private static final int PLAYING = 1;// 播放狀態(tài)private static final int PAUSING = 2;// 暫停狀態(tài)private static final int STOPPING = 3;// 停止?fàn)顟B(tài)private volatile int CURRENT = 0;// 當(dāng)前狀態(tài)private Timer timer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sb = (SeekBar) findViewById(R.id.sb);//設(shè)置拖動(dòng)監(jiān)聽sb.setOnSeekBarChangeListener(this);}//播放public void play(View view) {if (player != null) {if (CURRENT == PLAYING) {Toast.makeText(this, "音樂已經(jīng)在播放了", Toast.LENGTH_SHORT).show();return;} else if (CURRENT == PAUSING) {player.start();CURRENT = PLAYING;return;}}try {//創(chuàng)建一個(gè)播放器對(duì)象player = new MediaPlayer();//給播放器設(shè)置音樂路徑player.setDataSource("/mnt/sdcard/test.mp3");//設(shè)置音樂格式player.setAudioStreamType(AudioManager.STREAM_MUSIC);//準(zhǔn)備player.prepare();//獲取音樂最大長(zhǎng)度(毫秒單位)duration = player.getDuration();//給SeekBar 設(shè)置最大值sb.setMax(duration);//音樂開始播放player.start();//設(shè)置當(dāng)前的狀態(tài)為播放CURRENT = PLAYING;if (timer == null) {//創(chuàng)建定時(shí)器timer = new Timer();}/*** 參數(shù)1:匿名內(nèi)部類,相當(dāng)于Runnable 類* 參數(shù)2:第一次延時(shí)多長(zhǎng)時(shí)間(毫秒)后執(zhí)行,0 則代表立即執(zhí)行* 參數(shù)3:每隔多長(zhǎng)時(shí)間(毫秒)執(zhí)行一次*/timer.schedule(new TimerTask() {@Overridepublic void run() {//該方法每1 秒被調(diào)用一次if (CURRENT == PLAYING) {runOnUiThread(new Runnable() {@Overridepublic void run() {//雙重判斷,盡可能避免線程問題,因?yàn)樵摱未a時(shí)在主線程中的,//第一次判斷是在子線程中進(jìn)行的if (player != null && CURRENT == PLAYING) {//獲取當(dāng)前的播放進(jìn)度int currentPosition = player.getCurrentPosition();//設(shè)置給SeekBarsb.setProgress(currentPosition);}}});}}}, 0, 1000);} catch (Exception e) {e.printStackTrace();Toast.makeText(this, "音樂播放失敗" + e, 0).show();}}/*** 暫停*/public void pause(View view) {if (player != null && CURRENT == PLAYING) {player.pause();CURRENT = PAUSING;}}/*** 停止*/public void stop(View view) {if (player != null) {if (CURRENT == PLAYING || CURRENT == PAUSING) {CURRENT = STOPPING;//取消定時(shí)器timer.cancel();timer = null;player.stop();player.reset();player.release();player = null;sb.setProgress(0);}}}/** 拖動(dòng)過程中回調(diào)多次*/@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {if (player == null) {sb.setProgress(0);} else {player.seekTo(progress);}}/** 開始拖動(dòng)前回調(diào)一次*/@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {if (player == null) {Toast.makeText(this, "音樂播放器還未開始", Toast.LENGTH_SHORT).show();}}/** 結(jié)束拖動(dòng)后回調(diào)一次*/@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}@Overrideprotected void onDestroy() {super.onDestroy();stop(null);}}3、播放本地res/raw/目錄下的資源
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1); mediaPlayer.start(); // no need to call prepare(); create() does that for you4、播放本地URI資源
Uri myUri = ....; // initialize Uri here MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDataSource(getApplicationContext(), myUri); mediaPlayer.prepare(); mediaPlayer.start();5、播放網(wǎng)絡(luò)資源
String url = "http://........"; // your URL here MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDataSource(url); mediaPlayer.prepare(); // might take long! (for buffering, etc) mediaPlayer.start();6、異步準(zhǔn)備
String url = "http://........"; // your URL hereMediaPlayer mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(url);mediaPlayer.prepareAsync();mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();}});7、在后臺(tái)Service異步執(zhí)行播放任務(wù)
public class MyService extends Service implements MediaPlayer.OnPreparedListener {MediaPlayer mMediaPlayer;private static final String ACTION_PLAY = "com.example.action.PLAY";MediaPlayer mMediaPlayer = null;public int onStartCommand(Intent intent, int flags, int startId) {...if (intent.getAction().equals(ACTION_PLAY)) {mMediaPlayer = ... // initialize it heremMediaPlayer.setOnPreparedListener(this);mMediaPlayer.prepareAsync(); // prepare async to not block main thread}}/** Called when MediaPlayer is ready */public void onPrepared(MediaPlayer player) {player.start();} }public void initMediaPlayer() {// ...initialize the MediaPlayer here...mMediaPlayer.setOnErrorListener(this);}@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// ... react appropriately ...// The MediaPlayer has moved to the Error state, must be reset!}8、在手機(jī)睡眠時(shí)使用喚醒鎖
mMediaPlayer = new MediaPlayer(); // ... other initialization here ... mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//wifi鎖 WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");wifiLock.acquire();//當(dāng)暫停,不再需要網(wǎng)絡(luò)時(shí)釋放鎖 wifiLock.release();9、在前臺(tái)服務(wù)運(yùn)行播放任務(wù)
String songName; // assign the song name to songName PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,new Intent(getApplicationContext(), MainActivity.class),PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new Notification(); notification.tickerText = text; notification.icon = R.drawable.play0; notification.flags |= Notification.FLAG_ONGOING_EVENT; notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample","Playing: " + songName, pi); startForeground(NOTIFICATION_ID, notification);
10、處理音頻焦點(diǎn)
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {// could not get audio focus. }requestAudioFocus() 方法的第一個(gè)參數(shù)是一個(gè) AudioManager.OnAudioFocusChangeListener,當(dāng)任何時(shí)候音頻焦點(diǎn)發(fā)生變化的時(shí)候,會(huì)回調(diào) OnAudioFocusChangeListener的onAudioFocusChange()方法
class MyService extends Serviceimplements AudioManager.OnAudioFocusChangeListener {// ....public void onAudioFocusChange(int focusChange) {// Do something based on focus change...} }11、播放完畢時(shí)手動(dòng)釋放資源
public class MyService extends Service {MediaPlayer mMediaPlayer;// ...@Overridepublic void onDestroy() {if (mMediaPlayer != null) mMediaPlayer.release();} }三、Mp3文件簡(jiǎn)介
ID3
一般是位于一個(gè)mp3文件的開頭或末尾的若干字節(jié)內(nèi),附加了關(guān)于該mp3的歌手,標(biāo)題,專輯名稱,年代,風(fēng)格等信息,該信息就被稱為ID3信息,ID3信息分為兩個(gè)版本,v1和v2版。 其中:v1版的ID3在mp3文件的末尾128字節(jié),以TAG三個(gè)字符開頭,后面跟上歌曲信息。 v2版一般位于mp3的開頭,可以存儲(chǔ)歌詞,該專輯的圖片等大容量的信息。
V1與V2
- ID3V1記錄在MP3文件的末尾,長(zhǎng)度固定
- ID3V2就記錄在MP3文件的首部。 ID3V2一共有4個(gè)版本,但流行的播放軟件一般只支持第3版,既ID3v2.3。
- 對(duì)ID3V2的操作比ID3V1要慢。而且ID3V2結(jié)構(gòu)比ID3V1的結(jié)構(gòu)要復(fù)雜得多,但比ID3V1全面且可以伸縮和擴(kuò)展。
四、視頻播放器
1、SurfaceView
SurfaceView提供了一個(gè)繪畫的界面,你可以控制該界面的格式和大小,SurfaceView 負(fù)責(zé)在屏幕正確位置安置一個(gè)界面。SurfaceView的其中一個(gè)目的,是為了在子線程渲染屏幕,但需要注意幾點(diǎn)
- 所有SurfaceView 和SurfaceHolder.Callback 的方法,都必須在主線程調(diào)用
- 必須保證繪制線程,必須在SurfaceView 有效的情況下才能使用,也就是在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之間調(diào)用
SurfaceView是View的子類,等同于TextView、ImageView等一系列控件。核心功能可以通過子線程進(jìn)行界面的繪制,繪制需要注意的內(nèi)容:
所有SurfaceView和SurfaceHolder.Callback的方法都應(yīng)該在UI線程里調(diào)用,一般來說就是應(yīng)用程序主線程。渲染線程所要訪問的各種變量應(yīng)該作同步處理。
由于surface可能被銷毀,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之間有效,所以要確保渲染線程訪問的是合法有效的surface
- 雙緩沖技術(shù):內(nèi)存中有兩個(gè)畫布,A畫布顯示至屏幕,B畫布在內(nèi)存中繪制下一幀畫面,繪制完畢后B顯示至屏幕,A在內(nèi)存中繼續(xù)繪制下一幀畫面
- SurfaceView 是雙緩沖機(jī)制,一個(gè)用來緩沖數(shù)據(jù),另一個(gè)用來展現(xiàn)數(shù)據(jù),可以提高數(shù)據(jù)展示的速度,單緩沖需要先加載數(shù)據(jù)再去展示數(shù)據(jù),但是雙緩沖在展示第一個(gè)頁面數(shù)據(jù)的時(shí)候已經(jīng)把第二個(gè)頁面的數(shù)據(jù)加載好了
- 對(duì)畫面的實(shí)時(shí)更新要求較高,重量級(jí)組件,可見時(shí)才創(chuàng)建
- SurfaceView一旦不可見,就會(huì)被銷毀,一旦可見,就會(huì)被創(chuàng)建,銷毀時(shí)停止播放,再次創(chuàng)建時(shí)再開始播放
- 播放視頻也是用MediaPlayer,不過跟音頻不同,要設(shè)置顯示在哪個(gè)SurfaceView
2、使用MediaPlayer+SurfaceView 播放視頻
在該節(jié)中,視頻播放依然使用MediaPlayer 類,為了方便演示,我們直接使用本文中創(chuàng)建的工程,只需在布局文件添加SurfaceView 控件即可
布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context=".MainActivity"><SurfaceView android:id="@+id/sv"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout>實(shí)現(xiàn)代碼
public class MainActivity extends Activity {private MediaPlayer player;static int currentPosition;private SurfaceView sv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sv = (SurfaceView) findViewById(R.id.sv);//拿到surfaceview的控制器final SurfaceHolder sh = sv.getHolder();// Thread t = new Thread(){// @Override// public void run() {// try {// sleep(200);// } catch (InterruptedException e) {// e.printStackTrace();// }// runOnUiThread(new Runnable() {// @Override// public void run() {// MediaPlayer player = new MediaPlayer();// player.reset();// try {// player.setDataSource("sdcard/2.3gp");// player.setDisplay(sh);// player.prepare();// player.start();// } catch (Exception e) {// e.printStackTrace();// } // // }// });// // }// };// t.start();//SurfaceView是重量級(jí)組件,可見時(shí)才會(huì)創(chuàng)建//給SurfaceHolder設(shè)置CallBack,類似于偵聽,可以知道SurfaceView的狀態(tài)sh.addCallback(new Callback() {//surfaceView銷毀時(shí)調(diào)用@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {//每次surfaceview銷毀時(shí),同時(shí)停止播放視頻if(player != null){currentPosition = player.getCurrentPosition();player.stop();player.release();player = null;}}//surfaceView創(chuàng)建時(shí)調(diào)用@Overridepublic void surfaceCreated(SurfaceHolder holder) {//每次surfaceView創(chuàng)建時(shí)才去播放視頻if(player == null){player = new MediaPlayer();player.reset();try {player.setDataSource("sdcard/2.3gp");player.setDisplay(sh);player.prepare();player.start();player.seekTo(currentPosition);} catch (Exception e) {e.printStackTrace();}}}//surfaceView結(jié)構(gòu)改變時(shí)調(diào)用@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}});}}使用VideoView 播放視頻
VideoView 跟MediaPlayer 相比播放視頻步驟要簡(jiǎn)單的多,因?yàn)閂ideoView 原生提供了播放,暫停、快進(jìn)、快退、進(jìn)度條等方法。使用起來要方便的很多
1、設(shè)置布局文件,布局文件比較簡(jiǎn)單,因此這里只給你VideoView 標(biāo)簽
<VideoView android:id="@+id/vv"android:layout_width="match_parent"android:layout_height="match_parent" />2、設(shè)置VideoView 的播放文件路徑和媒體控制器,調(diào)用start 方法即可播放媒體文件
//實(shí)例化VideoView 對(duì)象 vv = (VideoView) findViewById(R.id.vv); //從界面獲取播放路徑 et_path = (EditText) findViewById(R.id.et_path);//給VideoView 設(shè)置視頻路徑 vv.setVideoPath(et_path.getText().toString()); //設(shè)置VideoView 控制器,我們當(dāng)前類實(shí)現(xiàn)了MediaPlayerControl 接口 vv.setMediaController(new MediaController(this)); //開始播放vv.start();//設(shè)置當(dāng)前播放器窗口設(shè)置為焦點(diǎn)vv.requestFocus();3、覆寫MediaPlayerControl 接口中的抽象方法
@Overridepublic void start() {}@Overridepublic void pause() {}@Overridepublic int getDuration() {return 0;}@Overridepublic int getCurrentPosition() {return 0;}@Overridepublic void seekTo(int pos) {}@Overridepublic boolean isPlaying() {return false;}@Overridepublic int getBufferPercentage() {return 0;}@Overridepublic boolean canPause() {return false;}@Overridepublic boolean canSeekBackward() {return false;}@Overridepublic boolean canSeekForward() {return false;}@Overridepublic int getAudioSessionId() {return 0;}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {return super.onKeyDown(keyCode, event);}@Overridepublic boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {return super.onKeyMultiple(keyCode, repeatCount, event);}注意:上面的方法都是回調(diào)方法,我們可以在這些方法里面實(shí)現(xiàn)我們的業(yè)務(wù)邏輯。只有當(dāng)我們給VideoView設(shè)置setMediaController 后控制器才會(huì)出現(xiàn)
收音機(jī)
- 播放協(xié)議: MMS
- MMS(Microsoft Media Server protocol)是一種串流媒體傳送協(xié)議 ,android并不支持這種流媒體協(xié)議
引入Vitamo框架進(jìn)行播放
- 核心類:io.vov.vitamio.MediaPlayer
- 操作:同系統(tǒng)的MediaPlayer,代碼編寫與播放網(wǎng)絡(luò)音樂相近
視頻處理
電影文件有很多基本的組成部分。首先,文件本身被稱為容器Container,容器的類型決定了信息被存放在文件中的位置。AVI和Quicktime就是容器的例子。接著,你有一組流,例如,你經(jīng)常有的是一個(gè)音頻流和一個(gè)視頻流。(一個(gè)流只是一種想像出來的詞語,用來表示一連串的通過時(shí)間來串連的數(shù)據(jù)元素)。在流中的數(shù)據(jù)元素被稱為幀F(xiàn)rame。每個(gè)流是由不同的編碼器來編碼生成的。編解碼器描述了實(shí)際的數(shù)據(jù)是如何被編碼Coded和解碼DECoded的,因此它的名字叫做CODEC。接著從流中被讀出來的叫做包Packets。包是一段數(shù)據(jù),它包含了一段可以被解碼成方便我們最后在應(yīng)用程序中操作的原始幀的數(shù)據(jù)。
七個(gè)模塊分別為:讀文件模塊,解復(fù)用模塊 ,視頻解碼模塊,音頻解碼音頻,顏色空間轉(zhuǎn)換模塊,視頻顯示模塊,音頻播放模塊
粗略的分為五類,分別是 Source filer, Demux flter, Decoder filter, Color Space converter filter,Render filter,各類 filter的功能與作用簡(jiǎn)述如下
Source filter
Source filter 源過濾器的作用是為下級(jí) demux filter 以包的形式源源不斷的提供數(shù)據(jù)流。在通常情況下,我們有多種方式可以獲得數(shù)據(jù)流,一種是從本地文件中讀取,一種是從網(wǎng)上獲取,Sourcefilter 另外一個(gè)作用就是屏蔽讀本地文件和獲取網(wǎng)絡(luò)數(shù)據(jù)的差別,在下一級(jí)的 demux filter 看來,本地文件和網(wǎng)絡(luò)數(shù)據(jù)是一樣的。
Demux filter
解復(fù)用過濾器的作用是識(shí)別文件類型,媒體類型,分離出各媒體原始數(shù)據(jù)流,打上時(shí)鐘信息后送給下級(jí) decoder filter。為識(shí)別出不同的文件類型和媒體類型,常規(guī)的做法是讀取一部分?jǐn)?shù)據(jù),然后遍歷解復(fù)用過濾器支持的文件格式和媒體數(shù)據(jù)格式,做匹配來確定是哪種文件類型,哪種媒體類型;有些媒體類型的原始數(shù)據(jù)外面還有其他的信息,比如時(shí)間,包大小,是否完整包等等。demux filter 解析數(shù)據(jù)包后取出原始數(shù)據(jù),有些類型的媒體不管是否是完整包都立即送往下級(jí) decoder filter,有些類型的媒體要送完整數(shù)據(jù)包,此時(shí)可能有一些數(shù)據(jù)包拼接的動(dòng)作;當(dāng)然時(shí)鐘信息的計(jì)算也是 demux filter 的工作內(nèi)容,這個(gè)時(shí)鐘用于各媒體之間的同步。在本例中,AVI Splitter 是 Demux filter。
Decoder filter
解碼過濾器的作用就是解碼數(shù)據(jù)包,并且把同步時(shí)鐘信息傳遞下去。對(duì)視頻媒體而言,通常是解碼成 YUV 數(shù)據(jù),然后利用顯卡硬件直接支持 YUV 格式數(shù)據(jù) Overlay 快速顯示的特性讓顯卡極速顯示。YUV格式是一個(gè)統(tǒng)稱,常見的有 YV12,YUY2,UYVY 等等。有些非常古老的顯卡和嵌入式系統(tǒng)不支持 YUV 數(shù)據(jù)顯示,那就要轉(zhuǎn)換成 RGB 格式的數(shù)據(jù),每一幀的每一個(gè)像素點(diǎn)都要轉(zhuǎn)換,分別計(jì)算 RGB 分量,并且因?yàn)檗D(zhuǎn)換是浮點(diǎn)運(yùn)算,雖然有定點(diǎn)算法,還是要耗掉相當(dāng)一部分 CPU,總體上效率底下;對(duì)音頻媒體而言,通常是解碼成 PCM 數(shù)據(jù),然后送給聲卡直接輸出。在本例中,AVI Decompress 和 ACM Warper 是 decoder filter。
Color space converter filter
顏色空間轉(zhuǎn)換過濾器的作用是把視頻解碼器解碼出來的數(shù)據(jù)轉(zhuǎn)換成當(dāng)前顯示系統(tǒng)支持的顏色格式。通常視頻解碼器解碼出來的是 YUV 數(shù)據(jù),PC 系統(tǒng)是直接支持 YUV 格式的,也支持 RGB 格式,有些嵌入式系統(tǒng)只支持 RGB 格式的。在本例中,視頻解碼器解碼出來的是 RGB8 格式的數(shù)據(jù),Color space converter filter 把 RGB8 轉(zhuǎn)換成 RGB32 顯示。
Render filter
渲染過濾器的作用就是在適當(dāng)?shù)臅r(shí)間渲染相應(yīng)的媒體,對(duì)視頻媒體就是直接顯示圖像,對(duì)音頻就是播放聲音。視音頻同步的策略方法有好幾種,其中最簡(jiǎn)單的一種就是默認(rèn)視頻和音頻基準(zhǔn)時(shí)間相同,這時(shí)音頻可以不打時(shí)鐘信息,通過計(jì)算音頻的采樣頻率,量化 bit 數(shù),聲道數(shù)等基本參數(shù)就知道音頻 PCM 的數(shù)據(jù)速率,按照這個(gè)速率往前播放即可;視頻必須要使用同步時(shí)鐘信息來決定什么時(shí)候顯示。DirectShow 采用一個(gè)有序鏈表 ,把接收到的數(shù)據(jù)包放進(jìn)有序鏈表中,啟動(dòng)一個(gè)定時(shí)器,每次定時(shí)器時(shí)間到就掃描鏈表,比較時(shí)鐘信息,或者顯示相應(yīng)的幀,或者什么也不做,每次接收到新的數(shù)據(jù)幀,首先判斷時(shí)鐘信息,如果是歷史數(shù)據(jù)幀就丟棄,如果是將來顯示數(shù)據(jù)幀就進(jìn)有序鏈表,如果當(dāng)前時(shí)間幀就直接顯示。如此這樣,保持視頻和音頻在人體感覺誤差范圍內(nèi)相對(duì)的動(dòng)態(tài)同步。在本例中 VideoRender 和 Default DirectSound Device 是 Render filter,同時(shí)也是 Sink filter
JetPlayer
JetPlayer jetPlayer = JetPlayer.getJetPlayer(); jetPlayer.loadJetFile("/sdcard/level1.jet"); byte segmentId = 0;// queue segment 5, repeat once, use General MIDI, transpose by -1 octave jetPlayer.queueJetSegment(5, -1, 1, -1, 0, segmentId++); // queue segment 2 jetPlayer.queueJetSegment(2, -1, 0, 0, 0, segmentId++);jetPlayer.play();TextureView
TextureView、Surfaceview、SurfaceTexture、GLSurfaceView
畫中畫
https://github.com/googlesamples/android-PictureInPicture
https://developer.android.google.cn/training/tv/playback/picture-in-picture.html?hl=zh-cn#handling_ui
總結(jié)
以上是生活随笔為你收集整理的Android多媒体开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android数据存储与持久化
- 下一篇: Android多媒体开发:照相机