Android音视频开发之使用SurfaceView绘图

96
落英坠露
2018.10.28 23:12* 字数 739

由于公司业务要用到音视频方面的知识,所以我打算学习一下 Android 音视频开发。在网上搜索资料和教程,发现系统化的比较少,大多讲得比较零散。Jhuster 的博客 Android 音视频开发入门指南 中有些任务列表,我觉得是一份非常好的学习指南。那么,今天就先从 SurfaceView 绘制开始。

SurfaceView 是 Android 中一种比较特殊的视图,它与视图容器并不是在同一个视图层上,绘制在一个独立的线程中完成,不需要及时响应用户的输入,也不会造成响应的 ANR 问题。SurfaceView 一般用在游戏、视频、摄影等一些复杂 UI 且高效的图像的显示,这类的图像处理都需要开单独的线程来处理。

View 和 SurfaceView 的区别:

1 . View 适用于主动更新的情况,而 SurfaceView 则适用于被动更新的情况,比如频繁刷新界面。

2 . View 在主线程中对页面进行刷新,而 SurfaceView 则开启一个子线程来对页面进行刷新。

3 . View 在绘图时没有实现双缓冲机制,SurfaceView 在底层机制中就实现了双缓冲机制。

关键接口 Callback:

Callback 是 SurfaceHolder 内部的一个接口,
接口中有以下三个方法

  • public void surfaceCreated(SurfaceHolder holder):Surface 第一次创建时被调用,例如 SurfaceView 从不可见状态到可见状态 。在这个方法被调用到 surfaceDestroyed 方法被调用之前,Surface 对象可以被操作。也就是说,在界面可见的情况下,可以对 SurfaceView 进行绘制。
  • public void surfaceChanged(SurfaceHolder holder, int format, int width, int height):Surface 大小和格式改变时会被调用,例如横竖屏切换时,如果需要对 Surface 的图像进行处理,就需要在这里实现。这个方法在 surfaceCreated 之后至少会被调用一次 。
  • public void surfaceDestroyed(SurfaceHolder holder):Surface 被销毁时被调用,例如 SurfaceView 从可见到不可见状态时。 在这个方法被调用过之后,不能够再对 Surface 对象进行任何操作,所以绘图线程不能再对 SurfaceView 进行操作。

SurfaceView 的使用方法:

  1. 实现 SurfaceHolder.Callback 接口
  2. 在 SurfaceHolder.Callback 的 surfaceCreated 方法中开启一个线程进行图像的绘制
  3. 在 SufaceHolder.Callback 的 surfaceDestroyed 方法中,结束绘制线程并调用 SurfaceHolder 的 removeCallbck 方法
  4. 在绘制线程每帧开始之前,调用 lockCanvas 方法锁住画布进行绘图
  5. 绘制完一帧的数据之后,调用 unlockCanvasAndPost 方法提交数据来显示图像

下面是一个示例代码:

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private final ILogger logger = LoggerFactory.getLogger(MySurfaceView.class);
    // 是否绘制
    private volatile boolean mIsDrawing;
    // SurfaceView 控制器
    private SurfaceHolder mSurfaceHolder;
    // 画笔
    private Paint mPaint;
    // 画布
    private Canvas mCanvas;
    // 独立的线程
    private Thread mThread;
    // 绘制的图像
    private Bitmap mBitmap;

    public MySurfaceView(Context context) {
        super(context);
        init();
    }

    public MySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mSurfaceHolder = getHolder();
        // 注册回调事件
        mSurfaceHolder.addCallback(this);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        logger.debug("onSurfaceCreated");
        mThread = new Thread(this, "Renderer");
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        logger.debug("onSurfaceChanged. format:{}, width:{}, height:{}", format, width, height);
        // 加载图像,并开启线程
        try {
            mBitmap = BitmapUtils.decodeSampledBitmapFromStream(getContext().getAssets().open("template.jpg"), width, height);
        } catch (IOException e) {
            logger.error(e);
        }
        mIsDrawing = true;
        mThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        logger.debug("onSurfaceDestroyed");
        // 不再绘制,移除回调,线程终止
        mIsDrawing = false;
        mSurfaceHolder.removeCallback(this);
        mThread.interrupt();
    }

    @Override
    public void run() {
        while (mIsDrawing) {
            logger.debug("draw canvas");
            // 锁定画布,获得画布对象
            mCanvas = mSurfaceHolder.lockCanvas();
            if (mCanvas != null) {
                try {
                    //使用画布做具体的绘制
                    draw();
                    // 线程休眠 100 ms
                    Thread.sleep(100);
                } catch (Exception e) {
                    logger.error(e);
                } finally {
                    // 解锁画布,提交绘制,显示内容
                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
                }
            }
        }
    }

    private void draw() {
        mCanvas.drawBitmap(mBitmap, 0, 0, mPaint);
    }
}

参考文章:

Android音视频
Web note ad 2