Android Camera2的基本使用(预览、拍照)

一、背景

需要使用Camera2来实时抓取每一帧的图像

二、Camera2相关的类

1.CameraManager

摄像头管理类,可以获取摄像头ID、摄像头支持分辨率、传感器旋转角度等摄像头信息。
同时打开关闭摄像头也需要通过这个类
以下是这个类的基本使用,用来获取摄像头信息

	private void getCameraInfo(){
		CameraManager cameraManager= (CameraManager) getSystemService(Context.CAMERA_SERVICE);
		try {
			cameraNames = cameraManager.getCameraIdList();
		} catch (CameraAccessException e) {
			throw new RuntimeException(e);
		}


		for (String cameraName : cameraNames) {
			StringBuilder info=new StringBuilder("cameraId:"+cameraName+"{\n");

			List<int[]> list=new ArrayList<>();
			CameraCharacteristics characteristics = null;
			try {
				characteristics = cameraManager.getCameraCharacteristics(cameraName);
			} catch (CameraAccessException e) {
				throw new RuntimeException(e);
			}
			int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
			info.append("sensorOrientation:"+sensorOrientation+",\n");
			Range<Integer>[] ranges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
			Log.e("lkx","range:"+ Arrays.toString(ranges));
			int lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
			Long aLong = characteristics.get(CameraCharacteristics.SENSOR_INFO_MAX_FRAME_DURATION);
			Log.e("lkx",cameraName+":"+aLong);
			switch (lensFacing) {
				case CameraMetadata.LENS_FACING_FRONT:
					info.append("lensFacing(前后摄):"+"front"+",\n");
					break;
				case CameraMetadata.LENS_FACING_BACK:
					info.append("lensFacing(前后摄):"+"back"+",\n");
					break;
				case CameraMetadata.LENS_FACING_EXTERNAL:
					info.append("lensFacing(前后摄):"+"external"+",\n");
					break;
			}
			info.append("}\n\n");

			Size[] supportedSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
				.getOutputSizes(ImageFormat.YUV_420_888);

			for (Size size : supportedSizes) {
				int width = size.getWidth();
				int height = size.getHeight();
				// 在这里处理支持的分辨率格式
				int[] temp={width,height};
				list.add(temp);
			}
			cameraInfoList.put(cameraName, String.valueOf(info));
			map.put(cameraName,list);
		}
	}

要获取摄像机的参数信息,主要是通过CameraCharacteristics这个类的get方法:
CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES FPS范围
SENSOR_ORIENTATION 传感器旋转角度,有的摄像头不是0度,需要根据传感器角度去调整预览和拍照的角度
LENS_FACING 前后摄
SENSOR_INFO_MAX_FRAME_DURATION 两帧之间的间隔,单位是纳秒 使用1秒除以这个参数可以计算出帧率
SCALER_STREAM_CONFIGURATION_MAP 支持的分辨率

2.CameraDevice

用于表示连接的相机设备

该类中有一个方法
CaptureRequest.Builder createCaptureRequest(int templateType)
用于创建一个CaptureRequest
其中传入的参数需要注意:

TEMPLATE_PREVIEW : 创建预览的请求
TEMPLATE_STILL_CAPTURE: 创建一个适合于静态图像捕获的请求,图像质量优先于帧速率
TEMPLATE_RECORD : 创建视频录制的请求
TEMPLATE_VIDEO_SNAPSHOT : 创建视视频录制时截屏的请求
TEMPLATE_ZERO_SHUTTER_LAG :创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量
TEMPLATE_MANUAL : 创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)

预览时需要传入TEMPLATE_PREVIEW

createCaptureSession
public abstract void createCaptureSession (List outputs,
CameraCaptureSession.StateCallback callback,
Handler handler)
outputs 目标Surface集
callback 创建CaptureSession的回调
handler 调用callback的线程
通过该方法创建一个CaptureSession

			mCameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()),
				new CameraCaptureSession.StateCallback() {
					@Override
					public void onConfigured(@NonNull CameraCaptureSession session) {
						if (mCameraDevice == null) {
							return;
						}
						cameraCaptureSession = session;
						updatePreview();
					}

					@Override
					public void onConfigureFailed(@NonNull CameraCaptureSession session) {
						Toast.makeText(MainActivity.this, "Configuration change", Toast.LENGTH_SHORT).show();
					}
				}, null);

3.CaptureRequest

表示一次操作请求,预览、拍照时都需要传入这个对象。
通过上面CameraDevice的createCaptureRequest可以获得CaptureRequest.Builder
CaptureRequest.Builder.build()就可以获得这个对象

4.CaptureRequest.Builder

CaptureRequest的构建器 通过CameraDevice的createCaptureRequest获取实例

addTarget(Surface outputTarget)
添加一个Surface到列表中作为此请求的输出目标
可以添加多个surface,但是重复添加无效

set
set(Key key, T value)
为CaptureRequest的字段设置值
例:
// 自动聚焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)

5.CameraCaptureSession

CameraDevice配置的一个捕获会话,调用此类的方法可以进行预览、拍照。
setRepeatingRequest(request: CaptureRequest, listener: CameraCaptureSession.CaptureCallback?, handler: Handler?)

							mCaptureSession.setRepeatingRequest(mPreviewRequest,
								mCaptureCallback, mBackgroundHandler);
								...
							private CameraCaptureSession.CaptureCallback mCaptureCallback
								= new CameraCaptureSession.CaptureCallback() {
						
								@Override
								public void onCaptureProgressed(@NonNull CameraCaptureSession session,
												@NonNull CaptureRequest request,
												@NonNull CaptureResult partialResult) {
									process(partialResult);
								}
						
								@Override
								public void onCaptureCompleted(@NonNull CameraCaptureSession session,
											       @NonNull CaptureRequest request,
											       @NonNull TotalCaptureResult result) {
									 process(result);
						
								}
						
							};

该方法创建一个无限捕捉图像的请求,可以用于预览,方法需要一个request,可传入回调函数

6.ImageReader

ImageReader类允许应用程序直接访问渲染成表面的图像数据

			mImageReader = ImageReader.newInstance(mWidth, mHeight,
				ImageFormat.YUV_420_888, /*maxImages*/60);
			mImageReader.setOnImageAvailableListener(
				mOnImageAvailableListener, mBackgroundHandler);

			//通过addTarget把imagereader的surface添加到预览的request里,这样可以实时获取每帧的图像数据
			mPreviewRequestBuilder.addTarget(mImageReader.getSurface());

	private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
		= new ImageReader.OnImageAvailableListener() {

		@Override
		public void onImageAvailable(ImageReader reader) {
		//由于添加到了预览的request,每获取到一帧图像都会回调这个方法
			Image mImage=reader.acquireNextImage();
			//获取到的image对象就是图片数据,此处可以对图片进行操作
			mImage.close(); //记得释放

		}


	};

三、Demo

此Demo仅仅实现最低需求的预览和拍照,代码需要完善。

逻辑代码

public class MainActivity extends AppCompatActivity {

	private static final String TAG = "Camera2Demo";
	private TextureView textureView;
	private CameraDevice mCameraDevice;
	private Size imageDimension;
	private CaptureRequest.Builder captureRequestBuilder;
	private CameraCaptureSession cameraCaptureSession;
	private ImageReader imageReader;

	private CaptureRequest captureRequest;


	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initPermission(); //权限申请
		textureView = findViewById(R.id.texture_view);
		textureView.setSurfaceTextureListener(textureListener);
		CameraCaptureSession.CaptureCallback CaptureCallback
			= new CameraCaptureSession.CaptureCallback() {

			@Override
			public void onCaptureCompleted(@NonNull CameraCaptureSession session,
						       @NonNull CaptureRequest request,
						       @NonNull TotalCaptureResult result) {
				//点击拍照后调用此回调 此处弹出提示
				Toast.makeText(MainActivity.this,"Saved:xx/xx/xx/xx.jpg",Toast.LENGTH_SHORT).show();
			}
		};
		findViewById(R.id.picture).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				try {
					//创建了一个拍照的请求
					final CaptureRequest.Builder captureBuilder =
						mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
					//与imageReader关联,当点击拍照获取到可用图片时,会触发ImageReader的回调,可在ImageReader的回调中对图片进行保存等处理
					captureBuilder.addTarget(imageReader.getSurface());
					cameraCaptureSession.capture(captureBuilder.build(),CaptureCallback,null);
				} catch (CameraAccessException e) {
					throw new RuntimeException(e);
				}
			}
		});
	}


	private final TextureView.SurfaceTextureListener textureListener =
		new TextureView.SurfaceTextureListener() {
			@Override
			public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
				Log.e(TAG, "onSurfaceTextureAvailable");
				openCamera();
			}

			@Override
			public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
				Log.e(TAG, "onSurfaceTextureSizeChanged");
			}

			@Override
			public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
				Log.e(TAG, "onSurfaceTextureDestroyed");
				return false;
			}

			@Override
			public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
				Log.e(TAG, "onSurfaceTextureUpdated");
			}
		};
	//相机状态变化时会调用这里的回调函数
	private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
		@Override
		public void onOpened(@NonNull CameraDevice camera) {
			//相机打开时执行
			Log.e(TAG, "onOpened");
			mCameraDevice=camera;
			//创建相机预览会话
			createCameraPreviewSession();
		}

		@Override
		public void onDisconnected(@NonNull CameraDevice camera) {
			//相机链接断开
		}

		@Override
		public void onError(@NonNull CameraDevice camera, int error) {

		}
	};

	private void openCamera() {
		CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
		try {
			//通过cameraId获取Camera参数
			String cameraId = cameraManager.getCameraIdList()[0];
			CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
			StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
			imageDimension=map.getOutputSizes(SurfaceTexture.class)[0];
			imageReader=ImageReader.newInstance(640,480, ImageFormat.YUV_420_888,10);
			imageReader.setOnImageAvailableListener(onImageAvailableListener,null);
			if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
				// TODO: Consider calling
				//    ActivityCompat#requestPermissions
				// here to request the missing permissions, and then overriding
				//   public void onRequestPermissionsResult(int requestCode, String[] permissions,
				//                                          int[] grantResults)
				// to handle the case where the user grants the permission. See the documentation
				// for ActivityCompat#requestPermissions for more details.
				return;
			}
			cameraManager.openCamera(cameraId, stateCallback, null);

		} catch (CameraAccessException e) {
			throw new RuntimeException(e);
		}
	}
	private boolean initPermission(){
		ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.CAMERA}, 1);
		// 高版本Android SDK时使用如下代码
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
			if(!Environment.isExternalStorageManager()){
				Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
				startActivity(intent);
				return false;
			}
		}



		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			if (checkSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
				requestPermissions(new String[] {Manifest.permission.CAMERA}, 1);
				return false;
			}
		}
		return true;
	}
	private void createCameraPreviewSession(){
		SurfaceTexture surfaceTexture=textureView.getSurfaceTexture();
		assert surfaceTexture!=null;

		surfaceTexture.setDefaultBufferSize(imageDimension.getWidth(),imageDimension.getHeight());
		//预览的输出画面
		Surface surface=new Surface(surfaceTexture);

		try {
			//预览请求
			captureRequestBuilder=mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
			captureRequestBuilder.addTarget(surface);
			mCameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()),
				new CameraCaptureSession.StateCallback() {
					@Override
					public void onConfigured(@NonNull CameraCaptureSession session) {
						if (mCameraDevice == null) {
							return;
						}
						cameraCaptureSession = session;
						updatePreview();
					}

					@Override
					public void onConfigureFailed(@NonNull CameraCaptureSession session) {
						Toast.makeText(MainActivity.this, "Configuration change", Toast.LENGTH_SHORT).show();
					}
				}, null);
		} catch (CameraAccessException e) {
			throw new RuntimeException(e);
		}


	}
	private void updatePreview(){
		captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
		captureRequest = captureRequestBuilder.build();

		try {
			cameraCaptureSession.setRepeatingRequest(captureRequest,null,null);
		} catch (CameraAccessException e) {
			throw new RuntimeException(e);
		}
	}
	private final ImageReader.OnImageAvailableListener onImageAvailableListener=
		new ImageReader.OnImageAvailableListener() {
			@Override
			public void onImageAvailable(ImageReader reader) {
				Image image=null;
				image=reader.acquireLatestImage();
				Log.e("lkx","1");
				//此处可对图片进行处理 比如保存到本地
				image.close();
			}
		};
}

布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <TextureView
        android:id="@+id/texture_view"
        android:layout_width="match_parent"
        android:layout_height="588dp"></TextureView>

    <Button
        android:id="@+id/picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="take_photo" />

</LinearLayout>

效果展示

在这里插入图片描述
启动apk,是一个预览框和一个拍照按钮

在这里插入图片描述
点击拍照,会弹出提示框
在这里插入图片描述
log也打印了,imageReader的回调正常调用

四、思考

对于刚开始提出来的需求,需要对每一帧图像进行实时抓取。通过这个demo我们不难发现,imageReader是通过addTarget添加到了拍照这个请求上,每次点击拍照,会生成一个可用图像,有了可用对象才会触发imageReader的回调。既然这样我们可以将imageReader通过addTarget绑定到预览的那个请求上,预览是无限获取每一帧的图像,每一帧都是一个可用图像,那么每一帧就都可以触发imageReader的回调函数,这样就可以在回调函数中对每一帧的图像进行处理。
需要注意,获取图像的帧率受很多因素影响,图片过大会使帧数降低,使用使应该尽量保证图片尺寸较小。初始化ImageReader时,newInstance方法的最后一个参数maxImage如果太小也会影响帧数。同时,也应该尽量保证对每一帧的图像数据处理耗时较低,可以通过Handler交给其他线程完成耗时任务。

The maxImages parameter determines the maximum number of Image objects that can be be acquired from the ImageReader simultaneously. Requesting more buffers will use up more memory, so it is important to use only the minimum number necessary for the use case.

上面是谷歌对maxImages这个参数的解释,翻译为中文:
maxImages参数决定可以同时从ImageReader获取的图像对象的最大数量。请求更多缓冲区将消耗更多内存,因此仅使用用例所需的最小数量是很重要的。

参数小会影响帧率,参数大会消耗更多内存。这两者之间需要进行平衡。

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 使用 camera2 API 可以更加灵活、可定制和高效地完成 Android 相机应用开发,其相比 camera1 API 的性能有大幅提升。 在使用 camera2 API 完成预览拍照前,需要进行以下几个步骤: 1. 获取 CameraManager 对象,查找可用的摄像头列表,并选择需要打开的摄像头。 2. 创建 CameraCaptureSession 对象,用于处理相机触发器的请求,并连接 CameraDevice 和 Surface。 3. 匹配预览和图片输出的 Surface,设置相应的尺寸和格式。 4. 创建 CaptureRequest 对象,设置相应的参数,如自动对焦模式、曝光模式等。 5. 使用 CameraCaptureSession 进行预览拍照。 在预览时,可以使用 TextureView 或 SurfaceView 进行实时数据渲染,比如显示相机预览画面、拍照后处理和显示等,同时可以通过设置监听器动态获取相机输出的图像流数据。 在拍照时,需要创建 ImageReader 对象,设置输出数据的格式和尺寸,同时需要建立对应的 Surface,将其传入 CaptureRequest.Builder,设置请求类型并发起拍照请求。通过设置 ImageReader 的 OnImageAvailableListener 接口,即可接收到图片数据,并进行后续处理和保存。 以上是使用 camera2 API 完成预览拍照基本流程,实际开发中需要根据具体需求进行优化和调整。 ### 回答2: Android Camera2 API 是 Android 系统中相机功能的一种全新的 API,使用 Camera2 可以更灵活地操作相机设备并获得更高质量的照片。 使用 Camera2 实现预览非常简单,我们只需要实现一个 CameraDevice.StateCallback 接口实现类和一个 SurfaceView 主界面。在 StateCallback 的 onOpened 回调中获得 CameraDevice 的实例,然后通过 ImageReader 创建 SurfaceHolder,最后将 SurfaceHolder 通过 CameraDevice.createCaptureSession 接口跟 CameraDevice 进行绑定即可实现预览拍照的实现过程与预览类似,首先获得 CameraDevice 实例,然后创建一个 CaptureRequest.Builder 对象,将拍照设置参数通过 CaptureRequest.Builder.set 方法设置到 CaptureRequest.Builder 对象中,最后通过 CameraCaptureSession.capture 接口启动拍照操作即可。 当然,在使用 Camera2 API 进行操作相机时,还需要注意一些其他问题,比如不同的相机设备有不同的特性,需要针对不同的设备进行优化和适配,还需要保证应用的流畅性和稳定性,以达到更好的用户体验。 总之,使用 Camera2 API 实现预览拍照Android 开发的一个重要技能,需要开发者深入了解该 API 的机制和使用方式,才能更好地实现优秀的相机应用。 ### 回答3: Android中的camera2是一种相机应用程序接口(API),旨在提高相机应用程序的功能和性能。相较于早期版本的camera API,camera2 API提供了更多的控制选项,允许开发者定制相机应用程序的功能,从而实现更好的用户体验。 使用camera2 API实现预览拍照需要以下步骤: 1. 获取CameraManager对象。使用该对象可以获取系统中可用的相机列表,并在需要的时候打开指定相机。 2. 打开指定相机。调用CameraManager.openCamera()方法打开相机。 3. 创建CaptureSession。CaptureSession是与相机关联的一组输出Surface的集合。 4. 创建CaptureRequest。CaptureRequest是一个指定相机操作和设置的重要对象,可以通过它来设置各种模式、参数和目标Surface。 5. 创建Preview Request。处理预览界面。 6. 启动相机预览。启动前,可以使用CaptureRequest.Builder设置其他预览参数。 7. 拍照。当用户点击拍照按钮时,调用CaptureSession.capture()方法,即可拍照并接收回调。 8. 关闭相机。释放所有占用的资源,以便其他应用程序可以使用相机。 总之,在使用camera2 API实现预览拍照时,需要使用许多类和方法。但只要开发者掌握了API中的基本概念和流程,就可以自由地使用该API,设计新型的相机应用程序,提供更好的功能和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值