一,問題描述
一般情況下我們通過手機播放器播放視頻的時候,都是通過UI介面上的按鈕,觸發播放事件。而顯示視頻流檔案的控制項SurfaceView則是跟隨播放進度重新整理每一幀的播放畫面。
然而,有時候,有時候……我們需要通過UI線程來代替按鈕事件,來達到控制視頻播放的效果。怎麼辦呢?有時候,光出現聲音,沒有畫面是怎麼回事呢?
二,問題解決
上一篇【Android】UI介面外的線程,控制重新整理UI介面中告訴我們通過Handler
對象重載其中方法 handleMessage(Message msg)來監聽其他線程發送來的訊息,從而更新UI介面顯示。這對一般的空間更新還是能起到效果的。但是對於SurfaceView 似乎不見效。
原因:SurfaceView 簡介(以下關於SurfacView介紹來源於百度文庫)
SurfaceView是視圖(View)的繼承類,這個視圖裡內嵌了一個專門用於繪製的Surface。你可以控制這個Surface的格式和尺寸。Surfaceview控制這個Surface的繪製位置。
surface是縱深排序(Z-ordered)的,這表明它總在自己所在視窗的後面。surfaceview提供了一個可見地區,只有在這個可見地區內的surface部分內容才可見,可見地區外的部分不可見。surface的排版顯示受到視圖層級關係的影響,它的兄弟視圖結點會在頂端顯示。這意味者
surface的內容會被它的兄弟視圖遮擋,這一特性可以用來放置遮蓋物(overlays)(例如,文本和按鈕等控制項)。注意,如果surface上面有透明控制項,那麼它的每次變化都會引起架構重新計算它和頂層控制項的透明效果,這會影響效能。
你可以通過SurfaceHolder介面訪問這個surface,getHolder()方法可以得到這個介面。
surfaceview變得可見時,surface被建立;surfaceview隱藏前,surface被銷毀。這樣能節省資源。如果你要查看
surface被建立和銷毀的時機,可以重載surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。
surfaceview的核心在於提供了兩個線程:UI線程和渲染線程。這裡應注意:
1> 所有SurfaceView和SurfaceHolder.Callback的方法都應該在UI線程裡調用,一般來說就是應用程式主線程。渲染線程所要訪問的各種變數應該作同步處理。
2> 由於surface可能被銷毀,它只在SurfaceHolder.Callback.surfaceCreated()和
SurfaceHolder.Callback.surfaceDestroyed()之間有效,所以要確保渲染線程訪問的是合法有效surface。
接下來呢,說說自己對它的理解
1、定義
可以直接從記憶體或者DMA等硬體介面取得映像資料,是個非常重要的繪圖容器。
它的特性是:可以在主線程之外的線程中向螢幕繪圖上。這樣可以避免畫圖任務繁重的時候造成主線程阻塞,從而提高了程式的反應速度。在遊戲開發中多用到SurfaceView,遊戲中的背景、人物、動畫等等盡量在畫布canvas中畫出。
2、實現
首先繼承SurfaceView並實現SurfaceHolder.Callback介面
使用介面的原因:因為使用SurfaceView有一個原則,所有的繪圖工作必須得在Surface被建立之後才能開始(Surface—表面,這個概念在 圖形編程中常常被提到。基本上我們可以把它當作顯存的一個映射,寫入到Surface的內容
可以被直接複製到顯存從而顯示出來,這使得顯示速度會非常快),而在Surface被銷毀之前必須結束。所以Callback中的surfaceCreated和surfaceDestroyed就成了繪圖處理代碼的邊界。
需要重寫的方法
implements SurfaceHolder.Callback
(1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//在surface的大小發生改變時激發
(2)public void surfaceCreated(SurfaceHolder holder){}
//在建立時激發,一般在這裡調用畫圖的線程。
(3)public void surfaceDestroyed(SurfaceHolder holder) {}
//銷毀時激發,一般在這裡將畫圖的線程停止、釋放。
整個過程:繼承SurfaceView並實現SurfaceHolder.Callback介面 ----> SurfaceView.getHolder()獲得SurfaceHolder對象 ---->SurfaceHolder.addCallback(callback)添加回呼函數---->SurfaceHolder.lockCanvas()獲得Canvas對象並鎖定畫布---->
Canvas繪畫 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)結束鎖定畫圖,並提交改變,將圖形顯示。
3、SurfaceHolder
這裡用到了一個類SurfaceHolder,可以把它當成surface的控制器,用來操縱surface。處理它的Canvas上畫的效果和動畫,控製表面,大小,像素等。
幾個需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 給SurfaceView當前的持有人一個回調對象。
(2)、abstract Canvas lockCanvas();
// 鎖定畫布,一般在鎖定後就可以通過其返回的畫布對象Canvas,在其上面畫圖等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 鎖定畫布的某個地區進行畫圖等..因為畫完圖後,會調用下面的unlockCanvasAndPost來改變顯示內容。
// 相對部分記憶體要求比較高的遊戲來說,可以不用重畫dirty外的其它地區的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 結束鎖定畫圖,並提交改變。
三,說了這麼多廢話,不知道您有沒有不耐煩?反正我自己解決這個問題的時候,有些耐不住了。因為網上這方面的資料難找。
說白了,要想實現音頻和視頻同步,需要在SurfaceView建立完成後才能正常播放音頻視頻同步檔案。所以需要在方法中啟動控制視頻播放、停止訊號的線程: public void surfaceCreated(SurfaceHolder holder){}
四,
五,程式碼範例
import java.util.Timer;import java.util.TimerTask;import android.app.Activity;import android.graphics.PixelFormat;import android.os.Bundle;import android.util.*;import android.view.SurfaceHolder;import android.view.SurfaceView;import android.view.View;import android.widget.ImageButton;import android.widget.SeekBar;import android.widget.TextView;import android.widget.Toast;import android.media.AudioManager;import android.media.MediaPlayer;import android.os.Handler;import android.os.Message;import android.widget.Button;public class hello extends Activity implements SurfaceHolder.Callback { private TextView mTextView; private MediaPlayer mMediaPlayer; private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder01; private ImageButton mPlay; private ImageButton mPause; private ImageButton mReset; private ImageButton mStop; private Button all_release; private boolean bIsPlay = false; private boolean bIsPaused = false; private boolean bIsReleased = false; private String strVideoPath = ""; public SeekBar seekBar=null; private Timer mTimer; private TimerTask mTimerTask; private boolean isChanging=false; String java_string="/sdcard/a.3gp?formatID=784444"; int allTime,currentTime; public static int action = 1;//1:play,2:stop,3:pause,4:resume,5:seek, Handler handler; void addTime(){ mTimer = new Timer(); mTimerTask = new TimerTask() { public void run() { if(isChanging==true)//拖動按鈕時候 return; else seekBar.setProgress(mMediaPlayer.getCurrentPosition()); } }; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mTextView = (TextView)findViewById(R.id.myTextView1); getWindow().setFormat(PixelFormat.UNKNOWN); mSurfaceView = (SurfaceView) findViewById(R.id.mSurfaceView1); mSurfaceHolder01 = mSurfaceView.getHolder(); mSurfaceHolder01.addCallback(this); mSurfaceHolder01.setFixedSize(176,144); mSurfaceHolder01.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); seekBar=(SeekBar)findViewById(R.id.seekBar); seekBar.setOnSeekBarChangeListener(new SeekBarChangeEvent()); mPlay = (ImageButton) findViewById(R.id.play); mPause = (ImageButton) findViewById(R.id.pause); mReset = (ImageButton) findViewById(R.id.reset); mStop = (ImageButton) findViewById(R.id.stop); all_release= (Button) findViewById(R.id.all_release); strVideoPath=java_string.substring(0, java_string.lastIndexOf('?')); /*************紅色*******************/ handler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what) { case 1:mPlay.performClick();break; case 2:mStop.performClick();break; case 3:mPause.performClick();break; case 4:mReset.performClick();break; case 5:break; default:break; } } }; /*************紅色*******************/ mPlay.setOnClickListener(new ImageButton.OnClickListener() { public void onClick(View view) { if(!bIsPlay)//û�в��ŵ�ʱ��{ bIsPlay=true; mPlay.setEnabled(false);//�ð���ʧ�� playVideo(strVideoPath); seekBar.setMax(mMediaPlayer.getDuration()); Toast.makeText(hello.this, "Total Time:"+mMediaPlayer.getDuration(), 5000).show(); addTime(); mTimer.schedule(mTimerTask, 0, 500); //dmr_update_message("PLAY",0);//�ϴ����� } } }); mPause.setOnClickListener(new ImageButton.OnClickListener() { public void onClick(View view) { // bIsPlay=!bIsPlay; if (mMediaPlayer != null) { if(bIsReleased == false) { if(bIsPaused==false) { mMediaPlayer.pause(); bIsPaused = true; mTextView.setText(R.string.str_pause); } else if(bIsPaused==true) { mMediaPlayer.start(); bIsPaused = false; mTextView.setText(R.string.str_play); } } } } }); mReset.setOnClickListener(new ImageButton.OnClickListener() { public void onClick(View view) { if(bIsReleased == false) { if (mMediaPlayer != null) { mMediaPlayer.seekTo(0); } } } }); mStop.setOnClickListener(new ImageButton.OnClickListener() { public void onClick(View view) { if (mMediaPlayer != null) { if(bIsReleased==false) { mTimer.cancel();//��ֹͣȻ���ٿ�ʼ seekBar.setProgress(0); mMediaPlayer.stop(); mMediaPlayer.release(); bIsReleased = true; mPlay.setEnabled(true); bIsPlay=false; mTextView.setText(R.string.str_stop); } } } }); all_release.setOnClickListener(new Button.OnClickListener() { public void onClick(View arg0) { } }); } public void playVideo(String strPath){ mMediaPlayer = new MediaPlayer(); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setDisplay(mSurfaceHolder01); try { mMediaPlayer.setDataSource(strPath); } catch (Exception e) { mTextView.setText("setDataSource Exceeption:"+e.toString()); } try { mMediaPlayer.prepare(); } catch (Exception e) { mTextView.setText("prepare Exceeption:"+e.toString()); } mMediaPlayer.start(); bIsReleased = false; mTextView.setText(R.string.str_play); mMediaPlayer.setOnCompletionListener (new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer arg0) { mTextView.setText(R.string.str_stop); } }); } @Overridepublic void surfaceChanged(SurfaceHolder surfaceholder, int format, int w, int h){ Log.i(TAG, "Surface Changed");} @Overridepublic void surfaceCreated(SurfaceHolder surfaceholder){/*************紅色*******************/ new Thread() { @Overridepublic void run(){ switch(action) { case 1:handler.sendEmptyMessage(1);break; case 2:handler.sendEmptyMessage(2);break; case 3:handler.sendEmptyMessage(3);break; case 4:handler.sendEmptyMessage(4);break; case 5:handler.sendEmptyMessage(5);break; default:break; }} }.start();/*************紅色*******************/} @Overridepublic void surfaceDestroyed(SurfaceHolder surfaceholder){ Log.i(TAG, "Surface Destroyed");} class SeekBarChangeEvent implements SeekBar.OnSeekBarChangeListener { @Override public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { isChanging=true; } @Override public void onStopTrackingTouch(SeekBar seekBar) { mMediaPlayer.seekTo(seekBar.getProgress()); isChanging=false; } } }
六,解釋:本例子有用的部分為紅色標記部分,實現功能為:通過改變全域訊號action的值來控制視頻播放,這裡沒有完善action如何改變,已經迴圈監聽action的部分代碼。只是希望你能理解,如何解決音頻、視頻不同步的問題。
限於筆者水平,有不當之處還請指出。