Android使用SurfaceView和TextureView预览Camera,获取NV21数据¶
更新: 2021-12-20 增加自动对焦说明
- 2021-12-20 增加自动对焦说明
- 2021-12-10 更新说明和参考
- 2018-2-16 创建文档
使用 android.hardware.Camera 进行视频的采集,分别使用 SurfaceView、TextureView 来预览 Camera 数据,取到NV21的数据回调。
准备¶
使用相机权限
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
不会显示东西。
不同于SurfaceView
,TextureView
不会建立一个单独的窗口,而是像一个常规的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中需要申明使用功能
先检查是否支持自动对焦,再去设置
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());
}
可以选择监听感应器变化,在里面发起对焦
参考资料¶
- Controlling the Camera - Android Developer
- Camera API - Android Developer
- TextureView - Android Developer
本站说明
一起在知识的海洋里呛水吧。广告内容与本站无关。如果喜欢本站内容,欢迎投喂作者,谢谢支持服务器。如有疑问和建议,欢迎在下方评论~