android-camera2预览拍照录制

Android 5.0(Lollipop)增加了Camera2 API,并将原有的Camera API标记为废弃。对于原有的Camera API来说,Camera2重新定义了相机的API,也重构了相机API的架构。在Camera2中其主要思想是基于会话模式和事件驱动与相机实现交互,对于预览、拍照、录制等操作都是在会话的基础下请求某种类型的会话操作。

比如一次拍照的操作:
拍照

下面一起看下camera2的操作:

  1. 相机初始化
    我们知道要使用相机,首先我们需要获得相关的权限,主要是在manifest中定义,其次在Android6.0还需要动态获取权限。
  • 在manifest中定义需要的权限
    1
    <uses-permission android:name="android.permission.CAMERA" />

如果要保存照片、录制视频,还需要两个权限:

1
2
3
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

相机功能:相机特性,如:

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

  • 动态权限申请
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private final String[] VIDEO_PERMISSIONS = {
    Manifest.permission.CAMERA,
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    };
    //......
    if (!hasPermissionsGranted(getApplicationContext(), VIDEO_PERMISSIONS)) {
    requestPermissions(VIDEO_PERMISSIONS, 1);
    return;
    }
    //......

    hasPermissionsGranted(Context context, String[] permissions) {
    for (String permission : permissions) {
    if (ActivityCompat.checkSelfPermission(context, permission)
    != PackageManager.PERMISSION_GRANTED) {
    return false;
    }
    }
    return true;
    }

以上基础工作好了基本可以开始对相机操作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//初始化相机设备
private void initCamera() {
//设备管理类
cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//获取相机设备特征类,通过该类可以获取相机的一些特性,如相机的方向
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraManager.getCameraIdList()[0]);
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
Log.e(TAG, "sensor_orientation is :" + mSensorOrientation);
StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//获取录制视屏时的宽高,这个通过MediaRecorder类获取系统支持的录制视频的宽高,主要是防止配置录制适配配置时失败
mVideoSize = chooseVideoSize(streamConfigurationMap.getOutputSizes(MediaRecorder.class));
//根据录制视频支持的宽高和SurfaceTexture支持的宽高,以及当前视图的宽高设置预览视图的宽高
mPreviewSize = chooseOptimalSize(streamConfigurationMap.getOutputSizes(SurfaceTexture.class), surfaceView.getWidth(), surfaceView.getHeight(), mVideoSize);
//imageReader初始化,用于获取拍照信息
imageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 2);
imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
imageView.setVisibility(View.VISIBLE);
// 拿到拍照照片数据
Image image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);//由缓冲区存入字节数组
final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
updatePreView();
}
}, mainHandler);
//打开摄像头,stateCallback为相机的状态监听回调
cameraManager.openCamera(cameraManager.getCameraIdList()[0], stateCallback, mainHandler);
mMediaRecorder = new MediaRecorder();
Log.d(TAG, "open camera");

} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//开启相机后有一个回调,stateCallback,该回调是用来返回相机是否正常打开的状态的开启相机后有一个回调,stateCallback,该回调是用来返回相机是否正常打开的状态的
private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.d(TAG, "camera open");
mCameraDevice = cameraDevice;
takePreview();
}

@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
Log.d(TAG, "camera onDisconnected");

if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
}

@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
Log.d(TAG, "camera onError");
cameraDevice.close();
mCameraDevice = null;
Toast.makeText(ImageShowActivity.this, "摄像头开启失败", Toast.LENGTH_SHORT).show();
}
};

  1. 开启相机预览

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    /**
    * 开始预览,此处创建一个捕获视频信息的请求,以此来获取一个会话session,在获取会话时监听其配置状态,一旦成功,则此时通过会话构建一个重复预览的请求;
    */
    private void takePreview() {
    try {
    closePreviewSession();
    SurfaceTexture surfaceTexture = surfaceView.getSurfaceTexture();
    surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    Log.e(TAG, "preview SurfaceTexture buffer size is width:" + mPreviewSize.getWidth() + " height :" + mPreviewSize.getHeight());
    Surface previewSurface = new Surface(surfaceTexture);
    List<Surface> surfaces = new ArrayList<>();
    surfaces.add(previewSurface);
    surfaces.add(imageReader.getSurface());
    // 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求
    mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() //
    {
    @Override
    public void onConfigured(CameraCaptureSession cameraCaptureSession) {
    Log.d("onConfigured", "onConfigured");
    if (null == mCameraDevice) return;
    // 当摄像头已经准备好时,开始显示预览
    mCameraCaptureSession = cameraCaptureSession;
    updatePreView();
    }

    @Override
    public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
    Log.d("onConfigureFailed", "onConfigureFailed");

    Toast.makeText(ImageShowActivity.this, "配置失败", Toast.LENGTH_SHORT).show();
    }
    }, childHandler);
    } catch (CameraAccessException e) {
    e.printStackTrace();
    }
    }
    /**
    * 更新预览视图,构建一个TEMPLATE_PREVIEW捕获请求,此时是对会话进行设置!!!setRepeatingRequest!!!
    */
    private void updatePreView() {
    try {
    // 创建预览需要的CaptureRequest.Builder
    CaptureRequest.Builder mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    // 将SurfaceView的surface作为CaptureRequest.Builder的目标
    SurfaceTexture surfaceTexture = surfaceView.getSurfaceTexture();
    Log.e(TAG, "update preview SurfaceTexture buffer size is width:" + mPreviewSize.getWidth() + " height :" + mPreviewSize.getHeight());
    surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    Surface previewSurface = new Surface(surfaceTexture);

    mPreviewBuilder.addTarget(previewSurface);
    mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
    mCameraCaptureSession.setRepeatingRequest(mPreviewBuilder.build(), null, childHandler);
    } catch (CameraAccessException e) {
    e.printStackTrace();
    }
    }
  2. 拍照

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 拍照,构建一个TEMPLATE_STILL_CAPTURE静态的相机信息捕获请求,需要注意的是需要获取ImageReader的surface并将其作为捕获去请求的目标输出。
*/
private void takePicture() {
if (mCameraDevice == null)
return;
// 创建拍照需要的CaptureRequest.Builder
try {
CaptureRequest.Builder captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 将surfaceHolder的surface作为CaptureRequest.Builder的目标
captureRequestBuilder.addTarget(imageReader.getSurface());
// 自动对焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 自动曝光
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 获取手机方向,手机竖屏和平板的方向是不同的,需要调整。
int rotation = getWindowManager().getDefaultDisplay().getRotation();
// 根据设备方向计算设置照片的方向
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
//拍照
CaptureRequest mCaptureRequest = captureRequestBuilder.build();
mCameraCaptureSession.capture(mCaptureRequest, null, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

/**
* Retrieves the JPEG orientation from the specified screen rotation.
*
* @param rotation The screen rotation.
* @return The JPEG orientation (one of 0, 90, 270, and 360)
*/
private int getOrientation(int rotation) {
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
// We have to take that into account and rotate JPEG properly.
// For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
}
  1. 录制视频
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* 视屏录制,比较上层api是直接通过Recorder类来实现录制,通过这个不需要自己对视屏数据进行处理,只需要指定具体编码格式即可,同时注意这里是重启一个会话。
*/
private void startRecordingVideo() {
//关闭预览会话
closePreviewSession();
//对Recoder类进行设置
setUpMediaRecorder();
try {
//创建录制的session会话中的请求
CaptureRequest.Builder mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
//向CaptureRequest添加surface
SurfaceTexture surfaceTexture = surfaceView.getSurfaceTexture();
Log.e(TAG, "video SurfaceTexture buffer size is width:" + mVideoSize.getWidth() + " height :" + mVideoSize.getHeight());
surfaceTexture.setDefaultBufferSize(mVideoSize.getWidth(), mVideoSize.getHeight());
Surface previewSurface = new Surface(surfaceTexture);
mPreviewBuilder.addTarget(previewSurface);
//向CaptureRequest添加surface
mPreviewBuilder.addTarget(mMediaRecorder.getSurface());


mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mMediaRecorder.getSurface()), new
CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mCameraCaptureSession = cameraCaptureSession;
updatePreView();
runOnUiThread(new Runnable() {
@Override
public void run() {
mMediaRecorder.start();

}
});
}

@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.d("onConfigureFailed", "onConfigureFailed");

Toast.makeText(ImageShowActivity.this, "RecordingVideo 配置失败", Toast.LENGTH_SHORT).show();
}
}, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 对Recorder类进行设置,主要包括音频、视频源、视频输出格式、输出路径、编码频率、视频帧频率、视频宽高、视频编码格式、音频编码格式
*/
private void setUpMediaRecorder() {
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//!!!这里需要设置路径,要不获取surface时会为空。context.getExternalFilesDir(null);
mMediaRecorder.setOutputFile(Environment
.getExternalStorageDirectory() + "/" + System.currentTimeMillis() + ".mp4");
mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
// 设置视频录制的分辨率。必须放在设置编码和格式的后面,否则报错!!!!!!需要小心设置,同时需要根据Recorder类来遴选出当前设备支持的分辨率,如果不恰当,则录制视频的时候会显示配置失败
Log.e(TAG, "video size is width:" + mVideoSize.getWidth() + " height :" + mVideoSize.getHeight());
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());

mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = getWindowManager().getDefaultDisplay().getRotation();
//调整视频的方向
switch (mSensorOrientation) {
case SENSOR_ORIENTATION_DEFAULT_DEGREES:
mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
break;
case SENSOR_ORIENTATION_INVERSE_DEGREES:
mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
break;
}
try {
mMediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}

手机支持的一些分辨率:
width:4608 height:3456
width:4608 height:2304
width:3456 height:3456
width:3840 height:2160
width:3280 height:2448
width:3264 height:2448
width:3264 height:1840
width:3264 height:1632
width:2448 height:2448
width:2592 height:1952
width:2048 height:1536
width:1920 height:1080
width:1440 height:1080
width:1536 height:864
width:1456 height:1456
width:1920 height:960
width:1440 height:720
width:1280 height:960
width:1280 height:720
width:960 height:720

参考地址:
[1].官网示例 https://github.com/googlesamples/android-Camera2Basic
[2]. https://blog.csdn.net/z_x_Qiang/article/details/77600880?locationNum=1&fps=1