[Android] 使用 Camera API 在 SurfaceView 及 TextureView 中预览数据并取到 NV21 回调

最近工作中接触到了关于渲染和获取 NV21 数据的一些东西,借此机会总结一下这些东西吧~

一、通过 Camera API (Camera1)使用相机

Android 5.0 之后推出了 Camera2,并已经将 Camera1 弃用了,但是为了适配 Android 5.0 之前的机型,Camera1 任然被很多人使用,这里我们介绍的是 Camera1。

使用 Camera 的基本步骤:

1、在 AndroidManifest 中声明权限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

如果是 Android 6.0 以上的系统还需要在代码中动态请求权限。

2、通过 mCamera = Camera.open(int cameraId) 获得相机实例,cameraId 可以在 0 和 mCamera.getNumberOfCameras() – 1 之间选择,代表前后摄像头。

3、如果需要修改相机的一些参数,可以通过 mCamera.getParameters() 获取到相机的参数,并且修改后通过 mCamera.setParameters(Camera.Parameters) 修改。

4、通过 mCamera.setDisplayOrientation(int) 修改展示的旋转角度,如果是竖屏参数为 90 才是正常的预览画面。

5、初始化一个 SurfaceHolder,然后通过 mCamera.setPreviewDisplay(SurfaceHolder) 设置预览的 surface,具体将在下面章节介绍。

6、通过 mCamera.startPreview() 即可开始预览摄像头捕获的画面了。

我们可以通过 takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback) 进行拍照并保存,在 Activity 不可见的时候(即对应生命周期的 onPause() 回调)需要对 Camera 进行释放,调用 ,mCamera.release() 方法即可。

 

二、将相机预览放到 SurfaceView 中

SurfaceView 是一个继承于 View 的类,而不同于 View 的是 SurfaceView 会开启一个子线程来刷新页面,经常被用于动画的渲染,并在底层实现了双缓冲机制。

将相机预览放到 SurfaceView 需要以下步骤:

1、创建一个 SurfaceView,并且实现 SurfaceHolder.Callback 接收 surface 的改变,其中包括 SurfaceView 创建 – surfaceCreated、改变 – surfaceChanged、销毁 -surfaceDestroyed。

2、获取 SurfaceHolder,SurfaceHolder 是 surface 的控制,可控制其大小、格式等,可以通过 mSurfaceView.getHolder() 取得。

3、将 Camera 的预览设置到此 SurfaceView 的 SurfaceHolder 中,通过 mCamera.setPreviewDisplay(mHolder) 设置。

4、相机开启预览:mCamera.startPreview();

此时即可在此 SurfaceView 中看到相机的预览画面了。参考代码:

public class SurfaceViewActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private final static String TAG = "SurfaceViewActivity";

    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_surface_view);

        SurfaceView surfaceView = findViewById(R.id.surface_view);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        if (Camera.getNumberOfCameras() > 0) {
            mCamera = Camera.open(0);
        } else {
            Log.i(TAG, "Error");
        }

    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        if (mCamera != null && surfaceHolder != null) {
            try {
                mCamera.setPreviewDisplay(surfaceHolder);
                mCamera.setDisplayOrientation(90);
                mCamera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        // 当 Surface 改变时调用
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        // 当 Surface 销毁时调用
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
        }
    }
}

 

三、将相机预览放到 TextureView 中

TextureView 必须在硬件加速开启的窗口中使用,不支持硬件加速的设备 TextureView 将不会显示任何东西,用于显示内容流,可以是视频流或是 OpenGL 场景。在使用中最大的不同就是 TextureView 可以改变 View 的大小,不像 SurfaceView 单独建立一个窗口。

将相机预览放到 TextureView 中的话,我们需要 setSurfaceTextureListener() 监听 TextureView 的变化,实例代码如下:

public class TextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {

    private static final String TAG = "TextureViewActivity";
    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_texture_view);

        TextureView textureView = findViewById(R.id.texture_view);
        textureView.setSurfaceTextureListener(this);

        if (Camera.getNumberOfCameras() > 0) {
            mCamera = Camera.open(0);
        } else {
            Log.i(TAG, "Error");
        }
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
        try {
            mCamera.setPreviewTexture(surfaceTexture);
            mCamera.setDisplayOrientation(90);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
        // 大小改变
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
        }
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        // Texture 刷新
    }
}

 

四、获取 NV21 回调

如果需要获取 NV21 的回调,在 Camera 在 release 之前设置 PreviewCallback 即可,比如:

mCamera.setPreviewCallback(new Camera.PreviewCallback() {
                    @Override
                    public void onPreviewFrame(byte[] bytes, Camera camera) {
                        
                    }
                });

其中 bytes 即为 NV21 数据。

 

五、参考资料

Android 官方 Camera 文档

Android 官方 SurfaceView 文档

Android API 指南 — Camera API

Android 官方 TextureView 文档

发表评论

电子邮件地址不会被公开。 必填项已用*标注