跳转至

Android使用SurfaceView和TextureView预览Camera,获取NV21数据

更新: 2021-12-20 增加自动对焦说明
  • 2021-12-20 增加自动对焦说明
  • 2021-12-10 更新说明和参考
  • 2018-2-16 创建文档

使用 android.hardware.Camera 进行视频的采集,分别使用 SurfaceView、TextureView 来预览 Camera 数据,取到NV21的数据回调。

Deprecated

android.hardware.Camera 已经不建议使用了。建议使用 android.hardware.camera2

可参考CameraX打开实时预览

获取nv21数据,可参考CameraX分析器

准备

使用相机权限

<uses-permission android:name="android.permission.CAMERA" />
camera预览回调中默认使用NV21格式。检查手机是否支持摄像头。

UI准备

<!-- 全屏显示 -->
<style name="FullScreenTheme" parent="AppTheme">
    <item name="windowNoTitle">true</item>
    <item name="android:windowFullscreen">true</item>
</style>

承载预览图像

<FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

使用 SurfaceView 预览 Camera,取到NV21数据

自定义CameraPreview继承SurfaceView,实现SurfaceHolder.Callback接口

获取NV21数据,Camera.setPreviewCallback() 要放在 Camera.startPreview() 之前。 使用Camera.PreviewCallback获取预览数据回调。默认是NV21格式。

surfaceChanged中,camera启动预览前可以进行设置,例如设置尺寸,调整方向

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;

/**
 * camera预览视图
 * Created by Rust on 2018/2/26.
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = "rustApp";
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private int mFrameCount = 0;

    public CameraPreview(Context context) {
        super(context);
    }

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void setCamera(Camera c) {
        this.mCamera = c;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // 开启预览
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 可在此释放camera
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // 若需要旋转、更改大小或重新设置,请确保证已停止预览
        if (mHolder.getSurface() == null) {
            return;
        }
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            // ignore: tried to stop a non-existent preview
        }
        Camera.Parameters parameters = mCamera.getParameters();
        // ImageFormat.NV21 == 17
        Log.d(TAG, "parameters.getPreviewFormat(): " + parameters.getPreviewFormat());
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.setPreviewCallback(mCameraPreviewCallback); // 回调要放在 startPreview() 之前
            mCamera.startPreview();
        } catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }

    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            mFrameCount++;
            Log.d(TAG, "onPreviewFrame: data.length=" + data.length + ", frameCount=" + mFrameCount);
        }
    };
}

为了防止阻塞UI线程,在子线程中打开camera。camera常放在try catch中使用。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "rustApp";

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new InitCameraThread().start();
    }

    @Override
    protected void onResume() {
        if (null == mCamera) {
            if (safeCameraOpen()) {
                mPreview.setCamera(mCamera); // 重新获取camera操作权
            } else {
                Log.e(TAG, "无法操作camera");
            }
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseCamera();
    }

    private boolean safeCameraOpen() {
        boolean qOpened = false;
        try {
            releaseCamera();
            mCamera = Camera.open();
            qOpened = (mCamera != null);
        } catch (Exception e) {
            Log.e(TAG, "failed to open Camera");
            e.printStackTrace();
        }
        return qOpened;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }

    private class InitCameraThread extends Thread {
        @Override
        public void run() {
            super.run();
            if (safeCameraOpen()) {
                Log.d(TAG, "开启摄像头");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mPreview = new CameraPreview(MainActivity.this, mCamera);
                        FrameLayout preview = findViewById(R.id.camera_preview);
                        preview.addView(mPreview);
                    }
                });
            }
        }
    }
}

运行在某些真机上,会发现图像被“拉高”了。

使用 TextureView 预览 Camera,取到NV21数据

TextureView可用于显示内容流。内容流可以是视频或者OpenGL的场景。内容流可来自应用进程或是远程其它进程。

Textureview必须在硬件加速开启的窗口中使用。若是软解,TextureView不会显示东西。

不同于SurfaceViewTextureView不会建立一个单独的窗口,而是像一个常规的View一样(个人认为这是个优点)。 这使得TextureView可以被移动,转换或是添加动画。比如,可以调用myView.setAlpha(0.5f)将其设置成半透明。

使用TextureView很简单:获取到它的SurfaceTexture,使用SurfaceTexture呈现内容。

CameraPreview继承了TextureView,外部需要传入camera实例。在onSurfaceTextureAvailable中,配置camera,比如设置图像方向。 通过设置Camera.PreviewCallback来取得预览数据。

import java.io.IOException;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import android.view.TextureView;

public class CameraPreview extends TextureView implements TextureView.SurfaceTextureListener {
    private static final String TAG = "rustApp";
    private Camera mCamera;

    public CameraPreview(Context context) {
        super(context);
    }

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
    }

    public void setCamera(Camera camera) {
        this.mCamera = camera;
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "TextureView onSurfaceTextureAvailable");
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }
        try {
            mCamera.setPreviewCallback(mCameraPreviewCallback);
            mCamera.setPreviewTexture(surface); // 使用SurfaceTexture
            mCamera.startPreview();
        } catch (IOException ioe) {
            // Something bad happened
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "TextureView onSurfaceTextureSizeChanged"); // Ignored, Camera does all the work for us
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        Log.d(TAG, "TextureView onSurfaceTextureDestroyed");
        mCamera.stopPreview();
        mCamera.release();
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // Invoked every time there's a new Camera preview frame
    }

    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Log.d(TAG, "onPreviewFrame: data.length=" + data.length);
        }
    };
}

操作界面TextureAct。获取camera操作权,初始化CameraPreview并添加到布局中。第一次获取camera时在子线程中操作。

onPause中释放camera,onResume中尝试取回camera控制权。这样应用暂时退回后台时,其他应用可以操作摄像头。

public class TextureAct extends AppCompatActivity {
    private static final String TAG = "rustApp";
    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_texture);
        new InitCameraThread().start();
    }

    @Override
    protected void onResume() {
        if (null == mCamera) {
            if (safeCameraOpen()) {
                mPreview.setCamera(mCamera); // 重新获取camera操作权
            } else {
                Log.e(TAG, "无法操作camera");
            }
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseCamera();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseCamera();
    }

    private boolean safeCameraOpen() {
        boolean qOpened = false;
        try {
            releaseCamera();
            mCamera = Camera.open();
            qOpened = (mCamera != null);
        } catch (Exception e) {
            Log.e(TAG, "failed to open Camera");
            e.printStackTrace();
        }
        return qOpened;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }

    private class InitCameraThread extends Thread {
        @Override
        public void run() {
            super.run();
            if (safeCameraOpen()) {
                Log.d(TAG, "TextureAct 开启摄像头");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mPreview = new CameraPreview(TextureAct.this, mCamera);
                        mPreview.setSurfaceTextureListener(mPreview);
                        FrameLayout preview = findViewById(R.id.camera_preview);
                        preview.addView(mPreview);
                    }
                });
            }
        }
    }
}
Textureview必须在硬件加速开启的窗口中使用。android:hardwareAccelerated="true" 默认的这个属性就是true,无需再设置。

每接到一帧数据,就会调用一次onSurfaceTextureUpdated()。通过这个接口。能够将上来的SurfaceTexture送给OpenGL再去处理。

启用自动对焦

manifest中需要申明使用功能

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

先检查是否支持自动对焦,再去设置

import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera; // 用的是camera1

Camera.Parameters parameters = camera.getParameters();
if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
camera.setParameters(parameters);

autoFocus方法要在camera.startPreview()camera.stopPreview()之间调用

camera.startPreview();
try {
    camera.autoFocus(new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            KCLog.d("对焦 " + success);
        }
    });
} catch (Exception e) {
    KCLog.e("对焦失败" + e.toString());
}

可以选择监听感应器变化,在里面发起对焦

参考资料

本站说明

一起在知识的海洋里呛水吧。广告内容与本站无关。如果喜欢本站内容,欢迎投喂作者,谢谢支持服务器。如有疑问和建议,欢迎在下方评论~

📖AndroidTutorial 📚AndroidTutorial 🙋反馈问题 🔥最近更新 🍪投喂作者

Ads