Camera Api2 预览,拍照,录像
1引言
1.1编写目的
Camera2 主要类的介绍,预览,拍照,录像的流程介绍。
1.2适用范围
Camera API2的使用。
2 主要类的介绍
-
CameraManager
是一个用于检测、连接和描述相机设备的系统服务,负责管理所有的CameraDevice相机设备。
可以通过调用Context.getSystemService(java.lang.String)方法来获取一个CameraManager的实例:
CameraManager manager=(CameraManager)getSystemService(Context.CAMERA_SERVICE);
-
CameraDevice
连接到 Android 设备的单个摄像头的表示,描述一个照相机设备,
一个 Android 设备可能会有多个摄像头,通过 CameraId 可以进行区别,当相机
状态回调函数执行的时候,可以从回调中拿到当前 CameraDevice,并通过
CameraDevice 对当前相机进行一些列操作。
-
CameraCharacteristics
是描述相机设备的属性类,其中的属性都是固定的,继承自 CameraMetadata 类。类比于旧 API 中的 CameraInfo 类。
包括:曝光补偿(Exposure compensation)、自动曝光/自动对焦/自动白平衡模式(AE / AF / AWB mode)、自动曝光/自动白平衡锁(AE / AWB lock)、自动对焦触发器(AF trigger)、拍摄前自动曝光触发器(Precapture AE trigger)、测量区域(Metering regions)、闪光灯触发器(Flash trigger)、曝光时间(Exposure time)、感光度(ISO Sensitivity)、帧间隔(Frame duration)、镜头对焦距离(Lens focus distance)、色彩校正矩阵(Color correction matrix)、JPEG 元数据(JPEG metadata)、色调映射曲线(Tonemap curve)、裁剪区域(Crop region)、目标 FPS 范围(Target FPS range)、拍摄意图(Capture intent)、硬件视频防抖(Video stabilization)等。
通过 CameraManager 的 getCameraCharacteristics(String cameraId) 方法获取指定相机设备的 CameraCharacteristics 对象。
-
CameraCaptureSession
是一个事务,用来向相机设备发送获取图像的请求。
主要有 setRepeatingRequest()
和 capture()
方法。
setRepeatingRequest()
是重复请求获取图像数据,常用于预览或连拍,capture()
是获取一次,常用于单张拍照。
CameraCaptureSession 类是一个抽象类.
-
CameraRequest
代表了一次捕获请求,从相机设备捕获单个图像所需的不可变的设置和输出包,当程序调用 setRepeatingRequest()方法进行预览时,或调用 capture()方法进行拍照时,都需要传入 CameraRequest 参数。
CameraRequest.Builder 则负责生成CameraRequest 对象
如:预览前设置自动对焦
CaptureRequestBuilder.set(CaptureRequest.CONTORL_AF_MODE,CaptureRequest.CONTROL_AF_MOODE_CONTINUOUS_PICTURE);
CaptureRequest = CaptureRequestBuilder.build();
CameraCaptureSession.setRepeatingRequest(CaptureRequest,CaptureCallback,Handler);
-
CaptureResult
CaptureRequest描述是从图像传感器捕获单个图像的结果的子集的对象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)当CaptureRequest被处理之后由CameraDevice生成。 -
CameraMetadata
Camera API2/HAL3架构下使用了全新的CameraMetadata结构取代了之前的SetParameter/Paramters等操作,实现了Java到native到HAL3的参数传递。
3 app 层的主要流程
3.1 预览的流程
3.1.1 先上图,看图讲故事
图1 预览流程图3.1.2 预览流程说明
- 获取CameraManager
//获取摄像头的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
- openCamera 打开相应ID的相机
·String CameraId:传入要打开的摄像头的 Id
·CameraDevice.stateCallback:相机的状态回调接口
·Handler handler:用来确定Callback在哪个线程执行,为null的话就在当前线程执行
public void openCamera( String cameraId,
final CameraDevice.StateCallback callback, Handler handler)
throws CameraAccessException {
openCameraForUid(cameraId, callback,
CameraDeviceImpl.checkAndWrapHandler(handler), USE_CALLING_UID);
}
- 实例化 CameraDevice.statCallback
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
mCameraDevice = camera;
//开启预览
startPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
}
};
- 生成CaptureRequest.Builder
private CaptureRequest.Builder mPreviewRequestBuilder;
// 创建预览请求的Builder(TEMPLATE_PREVIEW表示预览请求)
private void getPreviewRequestBuilder() {
try {
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
e.printStackTrace();
}
//设置预览的显示界面,即将显示预览用的 surface 的实例,作为一个显示层添加到该请求的目标列表中.
mPreviewRequestBuilder.addTarget(mPreviewSurface);
MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);
if (meteringRectangles != null && meteringRectangles.length > 0) {
CamLog.d("PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
}
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
}
- mCameraDevice.createCaptureSession 创建CaptureSession
·List<Surface>:捕获数据的输出Surface列表,此处传入用于显
示预览图像的 Surface 即可,即 Arrays.asList(surface),
·CameraCaptureSession.stateCallback:CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法.
·Handler:第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
public abstract void createCaptureSession( List<Surface> outputs,
CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException;
- CameraCaptureSession.StateCallback 回调
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
//该方法会从底层传回一个CameraCaptureSession,该 session 可以开始处理捕获请求,
mCaptureSession = session;
repeatPreview();
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
}
}
- CaptureRequest和mCaptureSession.setRepeatingRequest
private void repeatPreview() {
mPreviewRequestBuilder.setTag(TAG_PREVIEW);
mPreviewRequest = mPreviewRequestBuilder.build();
//设置反复捕获数据的请求,这样预览界面就会一直有数据显示
try {
mCaptureSession.setRepeatingRequest(mPreviewRequest,mPreviewCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
·CaptureReqeust:无限重复的请求,
·CameraCaptureSession.CaptureCallback:即 callback 的回调,不过预览中
该回调中不用进行任何处理
·Handler:第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
public abstract int setRepeatingRequest( CaptureRequest request,
CaptureCallback listener, Handler handler)
throws CameraAccessException;
3.2 拍照的流程
3.2.1 先上图,看图讲故事
图2 预览和拍照流程图3.2.2 拍照流程说明
- ImageReader 的使用
点击拍照,使用 ImageReader 访问呈现到 Surface 中的图像,并进行保存。在预览的 Surface 捕获图像的同时,我们也需要 ImageReader 来同时捕获图像数据,所以在预览的第五步中,CameraDevice.CreateCaptureSession()
方法中,我们将 ImageReader 的实例也传入第一个参数中,即 Arrays.asList(surface,ImageReader.getSurface())
private ImageReader mImageReader;
private void setupImageReader() {
//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.JPEG, 1);
//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
// 开启线程异步保存图片
new Thread(new ImageSaver(image)).start();
}
}, null);
}
public static class ImageSaver implements Runnable {
private Image mImage;
private File mImageFile;
public ImageSaver(Image image) {
mImage = image;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(mImageFile);
fos.write(data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImageFile = null;
if (fos != null) {
try {
fos.close();
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//预览第五步的代码
mCameraDevice.createCaptureSession(
Arrays.asList(mPreviewSurface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
mCaptureSession = session;
repeatPreview();
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
}
}, null);
- 生成CaptureRequest
//首先我们创建请求拍照的CaptureRequest
final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
mCaptureBuilder.addTarget(mPreviewSurface);
mCaptureBuilder.addTarget(mImageReader.getSurface());
//获取屏幕方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
//设置拍照方向
mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
CaptureRequest captureRequest = mCaptureBuilder.build();
- CameraCaputureSession.capture()方法进行拍照
· CaptureRequest:此次拍照的参数设置
· CaptureCallback:callback 对象,当这个请求被处理的时候触发,如果
为 null,不会生成 matedate 信息,但是仍会生成图像信息
· Handler:为一个句柄,代表执行 callback 的 handler,如果程序希望直
接在当前线程中执行 callback,则可以将 handler 参数设为 null
public abstract int capture( CaptureRequest request,
CaptureCallback listener, Handler handler)
throws CameraAccessException;
- CameraCaptureSession.CaptureCallback
//开始拍照,然后回调上面的接口重启预览,
// 因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
//恢复预览状态
repeatPreview();
}
};
3.3 录像的流程
3.3.1 先上图,看图讲故事
图3 预览、拍照、录像流程图3.3.2 录像流程说明
- MediaRecorder 的使用
MediaRecorder是安卓提供的一个用于音视频采集的类.
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
String mNextVideoAbsolutePath = Environment.getExternalStorageDirectory() + "/DCIM/videoBack.mp4";
mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mMediaRecorder.setOrientationHint(90);
try {
mMediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
- CaptureRequest.Builder的生成和添加Target
将 MedioRecorder 和预览用的 Surface 的实例,添加到该请求的目标列表中。
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
List<Surface> surfaces = new ArrayList<>();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// Set up Surface for the camera preview
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewRequestBuilder.addTarget(previewSurface);
// Set up Surface for the MediaRecorder
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewRequestBuilder.addTarget(recorderSurface);
- 将预览时创建的 session,close 掉
private void closePreviewSession() {
if (mCaptureSession != null) {
mCaptureSession.close();
mCaptureSession = null;
}
}
- createCaptureSession
·List<Surface>:新的用于捕获图像信息的 Surface 集合,此处为显示预览
信息的 surface 实例,以及记录图像信息用的 MediaRecorder 的实例
·CameraCaptureSession.StateCallback:用于通知新捕获 session 的
callback
·Handler:为一个句柄,代表执行 callback 的 handler,如果程序希望直
接在当前线程中执行 callback,则可以将 handler 参数设为 null
public abstract void createCaptureSession( List<Surface> outputs,
CameraCaptureSession.StateCallback callback,
Handler handler)
throws CameraAccessException;
- CameraCaptureSession.StateCallback
new CameraCaptureSession.StateCallback()
{
@Override
public void onConfigured (@NonNull CameraCaptureSession session){
mCaptureSession = session;
try {
//调用CaptureRequestBuilder.set()方法,设置捕获的参数,此处设置3A算法
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
CaptureRequest request = mPreviewRequestBuilder.build();
//调用CameraCaptureSession.setRepeatingReqest()方法,
//通过此捕获session,持续重复捕获图像
mCaptureSession.setRepeatingRequest(request, null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
// Start recording
mMediaRecorder.start();
}
});
}
@Override
public void onConfigureFailed (@NonNull CameraCaptureSession session){
}
}
- MediaRecorder 停止录制
public void videoStop(View view) {
mMediaRecorder.stop();
mMediaRecorder.reset();
Toast.makeText(this, "Video saved: 保存",
Toast.LENGTH_SHORT).show();
//重新开启预览
startPreview();
}