Android 音视频基础2——Android Camera API及Camera2 API采集视频数据

简介

两种API的基本说明

Camera API

打开摄像机,开启预览

Camera API 拍照流程

拍摄照片的一般步骤及API调用顺序

  1. 通过Camera#open(int) 来获取 Camera 实例;

  2. 通过Camera#getParameters(),来获取Camera默认设置;

  3. 如果需要修改Camera的设置,则修改Camera#getParameters()返回的Camera.Parameters对象,然后调用Camera#setParameters()设置Camera参数;

  4. 通过调用 Camera#setDisplayOrientation(int) 来获得正确的预览方向;

  5. 传递一个完全初始化好的SurfaceHolder(通过SurfaceView#getHolder()方法获取)给CamerasetPreviewDisplay(SurfaceHolder)方法,建立CameraSurfaceView的联系;

  6. 在开始调用Camera#takePicture()之前,一定要先调用Camera#startPreview()来开启预览;

  7. 调用 void takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback postview, PictureCallback jpeg) 来进行拍照,注意,在相应的回调中处理获取的图片数据;

  8. 调用 takePicture 之后,火锅还想继续拍照,需要重新调用 startPreview方法;

  9. 调用 stopPreview 来停止更新SurfaceView的预览;

  10. 在应用不使用相机时,需要调用release()方法来释放相机资源以供其他应用使用。在Activity onPause时应该立即释放相机资源,在Activity onResume 时在重新打开相机;

Camera API 视频录制模式

获取相机实例听开启预览(同拍照)
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
public void openCamera() {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
// 获取当前相机的设置参数
Camera.Parameters parameters = camera.getParameters();

// 获取相机预览的尺寸,这里获取的是相机预览支持的最大尺寸
Camera.Size previewSize = getCameraPreviewSize(parameters);
if (previewSize == null) {
LogUtils.w("Camera", "previewSize is null");
return;
}
int width = previewSize.width;
int height = previewSize.height;

final NV21EncoderH264 nv21EncoderH264 = new NV21EncoderH264(width, height);
nv21EncoderH264.setEncoderListener(this);

// 设置预览的格式为 ImageFormat.NV21
parameters.setPreviewFormat(ImageFormat.NV21);
parameters.setPreviewSize(width, height);
camera.setDisplayOrientation(90);
camera.setParameters(parameters);

try {
// 相机和surfaceView建立联系,预览内容会呈现在 指定的surfaceHoler上
camera.setPreviewDisplay(surfaceHolder);
} catch (IOException e) {
LogUtils.printStackTrace("Camera", e);
}
//设置监听获取视频流的每一帧
camera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
nv21EncoderH264.encoderH264(data);
}
});
//调用startPreview()用以更新preview的surface
camera.startPreview();
}

注意:使用相机需要 申请权限:Manifest.permission.CAMERA,Android 6.0后需要动态申请。

获取预览尺寸代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @Nullable
private Camera.Size getCameraPreviewSize(@NonNull Camera.Parameters parameters) {
List<Camera.Size> list = parameters.getSupportedPreviewSizes();
Camera.Size needSize = null;
for (Camera.Size size : list) {
if (needSize == null) {
needSize = size;
continue;
}
if (size.width >= needSize.width) {
if (size.height > needSize.height) {
needSize = size;
}
}
}
return needSize;
}
视频数据编码

定义一个视频编码工具类,将采集到的二进制数据(setPreviewFormat中指定预览的数据格式为 ImageFormat.NV21)进行数据转换,这里的示例是将其转换测h264格式数据。这里的编码采用的是MediaCodec。由于 MediaCodec 不直接支持 NV21格式,这里需要先将采集到的数据转换成 NV12格式,然后在将 NV12格式转换成 h264格式,示例代码如下:

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
public class NV21EncoderH264 {

private int width, height;
private int frameRate = 30;
private MediaCodec mediaCodec;
private EncoderListener encoderListener;

public NV21EncoderH264(int width, int height) {
this.width = width;
this.height = height;
initMediaCodec();
}

private void initMediaCodec() {
try {
mediaCodec = MediaCodec.createEncoderByType("video/avc");
//height和width一般都是照相机的height和width。
//TODO 因为获取到的视频帧数据是逆时针旋转了90度的,所以这里宽高需要对调
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", height, width);
//描述平均位速率(以位/秒为单位)的键。 关联的值是一个整数
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
//描述视频格式的帧速率(以帧/秒为单位)的键。帧率,一般在15至30之内,太小容易造成视频卡顿。
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
//色彩格式,具体查看相关API,不同设备支持的色彩格式不尽相同
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
//关键帧间隔时间,单位是秒
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//开始编码
mediaCodec.start();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 将NV21编码成H264
*/
public void encoderH264(byte[] data) {
//将NV21编码成NV12
byte[] bytes = NV21ToNV12(data, width, height);
//视频顺时针旋转90度
byte[] nv12 = rotateNV290(bytes, width, height);

try {
//拿到输入缓冲区,用于传送数据进行编码
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
//拿到输出缓冲区,用于取到编码后的数据
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
//当输入缓冲区有效时,就是>=0
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
//往输入缓冲区写入数据
inputBuffer.put(nv12);
//五个参数,第一个是输入缓冲区的索引,第二个数据是输入缓冲区起始索引,第三个是放入的数据大小,第四个是时间戳,保证递增就是
mediaCodec.queueInputBuffer(inputBufferIndex, 0, nv12.length, System.nanoTime() / 1000, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//拿到输出缓冲区的索引
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);

//outData就是输出的h264数据
if (encoderListener != null) {
encoderListener.h264(outData);
}

mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
} catch (Throwable t) {
LogUtils.printStackTrace("Camera", t);
}
}

/**
* 因为从MediaCodec不支持NV21的数据编码,所以需要先讲NV21的数据转码为NV12
*/
private byte[] NV21ToNV12(byte[] nv21, int width, int height) {
byte[] nv12 = new byte[width * height * 3 / 2];
int frameSize = width * height;
int i, j;
System.arraycopy(nv21, 0, nv12, 0, frameSize);
for (i = 0; i < frameSize; i++) {
nv12[i] = nv21[i];
}
for (j = 0; j < frameSize / 2; j += 2) {
nv12[frameSize + j - 1] = nv21[j + frameSize];
}
for (j = 0; j < frameSize / 2; j += 2) {
nv12[frameSize + j] = nv21[j + frameSize - 1];
}
return nv12;
}

/**
* 此处为顺时针旋转旋转90度
*
* @param data 旋转前的数据
* @param imageWidth 旋转前数据的宽
* @param imageHeight 旋转前数据的高
* @return 旋转后的数据
*/
private byte[] rotateNV290(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate the Y luma
int i = 0;
for (int x = 0; x < imageWidth; x++) {
for (int y = imageHeight - 1; y >= 0; y--) {
yuv[i] = data[y * imageWidth + x];
i++;
}
}
// Rotate the U and V color components
i = imageWidth * imageHeight * 3 / 2 - 1;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
i--;
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
i--;
}
}
return yuv;
}

/**
* 设置编码成功后数据回调
*/
public void setEncoderListener(EncoderListener listener) {
encoderListener = listener;
}

public interface EncoderListener {
void h264(byte[] data);
}
处理预览回调的数据

在camera.setPreviewCallback中处理回调的每一帧数据,调用上面的 encoderH264方法,将预览数据转换成h264数据,代码如下:

1
2
3
4
5
6
7
//设置监听获取视频流的每一帧
camera.setPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        nv21EncoderH264.encoderH264(data);
    }
});

在停止预览时,最终会调用 EncoderListener的 h264(byte[] data) 方法,之遥在这个方法中将 data保存成文件即可。

Camera2 API

Camera2 API 是Android 5.0 之后出现的相机操作API,主要用来替代原来的Camera API,Camera2 API提高了Android系统的拍照性能,支持RAW照片输出,还可以根据设备的支持情况,设置相机的对焦模式、曝光模式、快门等,相对于Camera API,对各个功能做了分层,使用起来更灵活。先对Camera2 API中几个常用的类进行说明,然后给出使用Camera2 API进行视频采集的一般流程。

  • CameraManager,相机管理类,是一个系统服务,需要通过该类可以检测、打开摄像头,同时可以获取该设备相机支持的特性;

  • CameraCharacteristics,设备相机属性描述类,具体描述了相机支持哪些功能特性;

  • CameraDevice,代表的是Android设备上的一个相机;

  • CameraCaptureSession,用于创建预览、拍照的Session类。通过它的setRepeatingRequest()方法控制预览界面 , 通过它的capture()方法控制拍照动作或者录像动作;

  • CameraRequest,一次捕获的请求,可以设置一些列的参数,用于控制预览和拍照参数,例如:对焦模式,曝光模式,zoom参数等等;

CameraCharacteristics

CameraCharacteristics是一个包含相机参数的对象,可以通过一些key获取对应的values。常用的参数如下:

  • LENS_FACING,获取摄像头的方向,

    • 其中 LENS_FACING_FRONT表示前置摄像头,

    • LENS_FACING_BACK表示后置摄像头,

    • LENS_FACING_EXTERNAL,表示外界摄像头;

  • SENSOR_ORIENTATION,获取摄像头拍照的方向;

  • FLASH_INFO_AVAILABLE,设备是否支持闪光灯;

  • SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,设备最大数码调焦值,即zoom的最大值;

  • LENS_INFO_MINIMUM_FOCUS_DISTANCE,获取最小的调焦距离,某些手机上获取到的该values为null或者0.0。前摄像头大部分有固定焦距,无法调节。

  • INFO_SUPPORTED_HARDWARE_LEVEL,获取摄像头支持某些特性的程度,以下是关于返回值的一些说明:

    • INFO_SUPPORTED_HARDWARE_LEVEL_FULL,功能支持最全的模式;

    • INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,限制模式,具体使用某个特性时,需要单独查询;

    • INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,向后兼容模式,支持的特性有限;

图片采集流程

  1. 检查设备兼容性;

  2. 打开星际并设置预览;

  3. 配置ImageReader

  4. 拍照处理;

  5. 恢复预览;

视频采集流程

  1. 检查设备兼容性,需要确保当前的设备是否支持Camera2 API的全部特性或部分特性;

  2. 打开相机和设置预览;

  3. 配置Media Recorder

  4. 开始录制;

  5. 停止录制;

使用Camera API或Camera2 API的流程大体上相同,Camera API操作都在Camera这个类中,所以所有操作都离不开Camera类或类实例,Camera2 API 由于对Camera的操作做了一些分层,使用流程有所不同。

检查设备兼容性

在使用Camera2 API 之前,先要确认当前硬件设备是否支持Camera2的特性,可以通过CameraManager.getCameraCharacteristics(String cameraId)来获取相机特性,并检查CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
// 不支持Camera2的高级功能
} else {
// 支持Camera2的高级功能
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
打开相机设置和设置预览
打开相机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 获取 CameraManager
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
// 获取 要打开的 cameraId,0表示获取后置摄像头
String cammeraId = manager.getCameraIdList()[0];
// 获取流配置信息,已用来设置相机预览和视频录制使用的尺寸
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// 获取视频录制尺寸和视频预览尺寸
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
mPreviewSize = choosePriview(map.getOutputSizes(MediaRecorder.SurfaceTexture.class));
// 根据当前屏幕的方向,来设置用来进行视频预览的 TextureView 的宽高
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
// 根据TextureView 的宽高和 预览的宽高,作相应的矩阵变化,并最终将这个变换应用到 TextureView中
configureTransform(width, height);
// 打开相机,并设置回调,第三个参数表示会调结果将在当前线程进行处理
manager.openCamera(cameraId, mStateCallback, null);

mStateCallback为相机打开状态会调监听,相机有三个状态可以进行会调处理:

  • onOpened,成功打开,之后可以开始预览了;

  • onDisconnected,断开链接,设备已不可在用了,需要进行 CameraDevice 关闭

  • onError,打开失败,同样要进行 CameraDevice 关闭操作;

具体实现如下:

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
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
// 打开成功后,开启预览
startPreview();
mCameraOpenCloseLock.release();
// 根据 TextureView的尺寸,应用矩阵变化
if (null != mTextureView) {
configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
}
}

@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}

@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
finish();
}

};
开启预览

上面的starPreview() 方法进行的就是开启相机预览的具体操作,一般来说,开启相机预览要经历一下步骤:

  1. 关闭之前的预览对话,即 如果之前存在 CameraCaptureSession 对象实例,需要先调用其close()方法;

  2. 通过 CaptureRequest.Builder 构造新的 CaptureRequest,核心是要调用 CaptureRequest.Builder的addTarget(Surface) 为预览指定Surface对象(通常从TexttureView、SurfaceView等获取),注意 CaptureRequest.Builder 通过调用CameraDevice的createCaptureRequest方法获取;

  3. 建立新的 CameraCaptureSession,并在建立成功后,更新预览即可,建立成功与否通过 CameraCaptureSession.StateCallback会调来判断;

  4. 不断的通过 CameraCaptureSession 发送 重复的 CaptureRequest,由于这里需要一直不断重复的发送,所以这里推荐采用单独的线程来进行操作;

通过上面的操作,就完成了相机的打开即预览开启的工作,接下来就是重头戏,视频的录制操作。

配置MediaRecorder

由是采用 MediaRecorder 来作为视频数据的输出端的,在开始录制之前,需要对 MediaRecorder进行相应的设置。

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
private void setUpMediaRecorder() throws IOException {
final Activity activity = getActivity();
// 设置声音来源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设置视频数据来源
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
// 设置输出格式
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
// 获取最终要保存的文件
if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) {
mNextVideoAbsolutePath = getVideoFilePath(getActivity());
}
// 设置给 MediaRecorder
mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
// 设置 编码 比特率
mMediaRecorder.setVideoEncodingBitRate(10000000);
// 设置帧率
mMediaRecorder.setVideoFrameRate(30);
// 设置视频尺寸
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
// 设置视频编码
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 设置音频编码
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = activity.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;
}
// 调用 Prepare,一切就绪了
mMediaRecorder.prepare();
}
开始录制

开始录制的过程其实和开启预览的过程差不多,区别在于以下几点:

  • 预览 mCameraDevice.createCaptureRequest采用的是TEMPLATE_PREVIEW,而录制采用的是TEMPLATE_RECORD

  • 预览CaptureRequest.Builder只添加了一个 previewSurface,即TextureView的Surface,而录制在此基础之上还添加了一个 recorderSurface,即MediaRecorder.getSurface();

注意:

如果要在 CameraCaputureSession 的会调中进行UI更新相关的操作,则需要进行线程的切换操作。

停止录制

结束录制很简单,终止相应的 CameraCaputureSession,调用 MediaRecorder的stop,并重置,然后再重新 调用前面的startPreview方法,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
private void stopRecordingVideo() {
// UI
mIsRecordingVideo = false;
mButtonVideo.setText("开始录制");
// Stop recording
mMediaRecorder.stop();
mMediaRecorder.reset();
Toast.makeText(getActivity(), "Video saved: " + mNextVideoAbsolutePath, Toast.LENGTH_LONG).show();
LogUtils.d(TAG, "Video saved: " + mNextVideoAbsolutePath);
mNextVideoAbsolutePath = null;
startPreview();
}

注意事项

  1. 使用相机相关API时,需要请求 android.permission.CAMERA,代码如下:

    1
    2
    3
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <uses-permission android:name="android.permission.CAMERA" />
    </manifest>

    注意6.0 之后,这个权限需要动态请求了。动态请求权限代码片段如下:

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
public class VideoCameraDemoActivity extends AppCompatActivity {

// 相机权限请求码
public static final int MY_PERMISSIONS_REQUEST_CAMERA = 10001;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// 在需要使用相机的地方检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {

// 如果应用程序之前请求过此权限但用户拒绝了请求,此方法将返回 true。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 显示解释为什么需要这个权限的对话框
// 这里可以提供一个解释为何需要相机权限的理由
} else {
// 直接请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
MY_PERMISSIONS_REQUEST_CAMERA); // 自定义请求码
}
}
}


// 请求权限的结果会在onRequestPermissionsResult()回调中处理
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_CAMERA: {
// 如果请求被取消,那么result数组将为空。
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被用户同意,可以继续使用相机功能
} else {
// 权限被用户拒绝,不能使用相机功能,可能需要提示用户并提供相应的备选方案
}
return;
}
// 其他'case'语句可以处理其他权限请求

}

}
  1. AndroidManifest.xml 中声明使用相机权限时,提示 Permission exists without corresponding hardware <uses-feature android:name="android.hardware.camera" required="false">,原因如下:

在大多数情况下,硬件是可选的,因此最好在 声明中将 android:required 设置为 false,从而将
硬件声明为可选项,并在应用中检查是否支持硬件。如果不添加这个声明,则 Android 会假定应用必须在有该硬件的情况下才能
运行。因此,系统会阻止某些设备安装应用

总结

本文主要对 Camera API 和 Camera2 API 进行了简单的介绍,并给出了两种API下进行视频采集的具体实现,资料一部分来源于网络,一部分来源于自己对官方文档的理解,其中Camera2 API实例是Google 官方的 Camera2 的实例,通过本文,了解到了 Camera2 API使用的具体流程,当然Camera2 API还有很多可以控制的细节,需要 日后不断完善。