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
    }
}
发布了44 篇原创文章 · 获赞 115 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/gs12software/article/details/104793198