Android音視頻開發(二)SurfaceView

簡介

官方API文檔介紹:SurfaceView是View的子類,它內嵌了一個專門用於繪製的Surface,你可以控制這個Surface的格式和尺寸,Surfaceview控制這個Surface的繪製位置。

surface是縱深排序(Z-ordered)的,這表明它總在自己所在窗口的後面。SurfaceView提供了一個可見區域,只有在這個可見區域內的surface內容纔可見。surface的排版顯示受到視圖層級關係的影響,它的兄弟視圖結點會在頂端顯示。這意味者 surface的內容會被它的兄弟視圖遮擋,這一特性可以用來放置遮蓋物(overlays)(例如,文本和按鈕等控件)。注意,如果surface上面有透明控件,那麼每次surface變化都會引起框架重新計算它和頂層控件的透明效果,這會影響性能。

你可以通過SurfaceHolder接口訪問這個surface,getHolder()方法可以得到這個接口。

SurfaceView變得可見時,surface被創建;SurfaceView隱藏前,surface被銷燬。這樣能節省資源。如果你要查看 surface被創建和銷燬的時機,可以重載surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。

SurfaceView的核心在於提供了兩個線程:UI線程和渲染線程。這裏應注意:

  • 所有SurfaceView和SurfaceHolder.Callback的方法都應該在UI線程裏調用,一般來說就是應用程序主線程。渲染線程所要訪問的各種變量應該作同步處理。
  • 由於surface可能被銷燬,它只在SurfaceHolder.Callback#surfaceCreated()和 SurfaceHolder.Callback#surfaceDestroyed()之間有效,所以要確保渲染線程訪問的是合法有效的surface。

注意:從平臺版本Build.VERSION_CODES.N開始,SurfaceView的窗口位置與其他View渲染同步更新。這意味着在屏幕上平移和縮放SurfaceView不會導致渲染失真。當它的窗口異步渲染時,可能會在之前的安卓版本中發生渲染失真。

雙緩衝技術

爲了防止動畫閃爍而實現的一種多線程應用。主要原理:一個畫面顯示過程中,程序又實時在改變它,前畫面還沒有顯示完,程序又請求重新繪製,這樣屏幕就會不停閃爍。爲了避免閃爍,可以使用雙緩衝技術,將要處理的圖片都在內存中處理好之後,再將其顯示到屏幕上。這樣顯示出來的總是完整的圖像,不會出現閃爍現象。
在內存中創建一個與屏幕繪圖區域一致的對象,先將圖形繪製到內存中的這個對象上,再一次性將這個對象上的圖形拷貝到屏幕上,這樣能大大加快繪圖的速度。

SurfaceView默認使用雙緩衝技術,它支持在子線程中繪製圖像,這樣就不會阻塞主線程了,所以它適合於視頻或圖像相關的開發。
參考:android雙緩衝繪圖技術分析

SurfaceView的使用

下面我將用代碼形式,展示如何使用SurfaceView。
一、繪製圖形

class ImageSurfaceView(context: Context?) : SurfaceView(context), SurfaceHolder.Callback {

    var surfaceHolder: SurfaceHolder? = null
    var drawThread: DrawThread? = null
    init {
        surfaceHolder = holder
        surfaceHolder?.addCallback(this)//監聽surface的狀態
        if (surfaceHolder != null) {
            drawThread = DrawThread(surfaceHolder!!)
        }
    }
    override fun surfaceCreated(holder: SurfaceHolder?) {
        //surface創建的時候調用,一般在該方法中啓動繪圖的線程
        Log.i("Test", "surface創建")
        drawThread?.isRunning = true
        drawThread?.start()
    }

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
        //surface尺寸發生改變的時候調用,如橫豎屏切換,或者初次渲染在屏幕上時
        Log.i("Test", "surface更新")
    }
    
    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        //surface被銷燬的時候調用,一般在該方法中停止繪圖線程
        Log.i("Test", "surface銷燬")
        drawThread?.isRunning = false
    }

    /**
     * 異步線程,不停地繪製文字
     */
    inner class DrawThread(val sHolder: SurfaceHolder) : Thread() {
        var isRunning = true
        var count = 0
        override fun run() {
            while (isRunning) {
                var c: Canvas? = null
                try {
                    synchronized(sHolder) {
                        c = sHolder.lockCanvas()//獲取畫布,以執行繪製
                        c?.drawColor(Color.WHITE)//畫布背景白色
                        val paint = Paint()
                        paint.color = Color.RED
                        paint.textSize = 60f
                        c?.drawText("${count++}秒", 200f, 500f, paint)//繪製時
                        sleep(1000)
                    }
                }catch (e:Exception){
                }finally {
                    if (c != null) {
                        sHolder.unlockCanvasAndPost(c)//釋放畫布並顯示所繪製的內容到屏幕上
                    }
                }
            }
        }
    }
}

二、播放視頻
使用MediaPlayer+SurfaceView播放視頻,安卓還有個VideoView是繼承SurfaceView專門用於播放視頻的,那個可以自行百度。

class SurfaceActivity : AppCompatActivity(), SurfaceHolder.Callback {
    //媒體播放控制器
    private var mediaPlayer: MediaPlayer? = null
    //視頻路徑
    private var videoPath: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_surface)

        ////把輸送給surfaceView的視頻畫面,直接顯示到屏幕上,不要維持它自身的緩衝區
        sv_video.holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
        sv_video.holder.setKeepScreenOn(true)
        sv_video.holder.addCallback(this)
        initMedia()
        btn_play.setOnClickListener {
            if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
                pause()
            } else {
                start()
            }
        }
    }

    /**
     * 初始化媒體播放
     */
    private fun initMedia() {
        videoPath = "/storage/emulated/0/DCIM/Camera/VID_20200311_082801.mp4"

        mediaPlayer = MediaPlayer()
        mediaPlayer?.setDataSource(videoPath)
        mediaPlayer?.setOnPreparedListener {
            start()//緩衝完,播放
        }
        mediaPlayer?.setOnCompletionListener {
            Toast.makeText(this,"播放完畢",Toast.LENGTH_SHORT).show()
            btn_play.text = "重新播放"
        }
    }
    
    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        if (mediaPlayer != null && mediaPlayer!!.isPlaying) {
            mediaPlayer!!.stop()
        }
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        mediaPlayer?.setDisplay(holder)//設置播放的容器,將MediaPlayer和SurfaceView關聯起來
        mediaPlayer?.prepareAsync()//緩衝
    }

    private fun start(){
        btn_play.text = "暫停"
        mediaPlayer?.start()
    }

    private fun pause(){
        btn_play.text = "播放"
        mediaPlayer?.pause()
    }

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
        mediaPlayer = null
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章