全部博文(478)
分类: Android平台
2018-05-07 14:44:39
现在Android智能手机的像素都会提供照相的功能,大部分的手机的摄像头的像素都在1000万以上的像素,有的甚至会更高。它们大多都会支持光学变焦、曝光以及快门等等。
下面的程序Demo实例示范了使用Camera v2来进行拍照,当用户按下拍照键时,该应用会自动对焦,当对焦成功时拍下照片。
<manifest xmlns:android="" package="com.fukaimei.camerav2test"> <uses-permission android:name="android.permission.CAMERA" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter> activity> application> manifest>
上面的程序的界面提供了一个自定义TextureView来显示预览取景,十分简单。该自定义TextureView类的代码如下:
package com.fukaimei.camerav2test; import android.content.Context; import android.util.AttributeSet; import android.view.TextureView; /**
* Created by FuKaimei on 2017/9/29.
*/ public class AutoFitTextureView extends TextureView { private int mRatioWidth = 0; private int mRatioHeight = 0; public AutoFitTextureView(Context context, AttributeSet attrs) { super(context, attrs);
} public void setAspectRatio(int width, int height) {
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
} @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else { if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
接来了的MainActivity.java程序将会使用CameraManager来打开CameraDevice,并通过 CameraDevice创建CameraCaptureSession,然后即可通过CameraCaptureSession进行预览或拍照了。
package com.fukaimei.camerav2test; import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.os.Build; import android.os.Bundle; import android.support.annotation.RequiresApi; import android.support.v4.app.ActivityCompat; import android.util.Log; import android.util.Size; import android.util.SparseIntArray; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public class MainActivity extends Activity implements View.OnClickListener { private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private static final String TAG = "MainActivity"; static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
} private AutoFitTextureView textureView; // 摄像头ID(通常0代表后置摄像头,1代表前置摄像头) private String mCameraId = "0"; // 定义代表摄像头的成员变量 private CameraDevice cameraDevice; // 预览尺寸 private Size previewSize; private CaptureRequest.Builder previewRequestBuilder; // 定义用于预览照片的捕获请求 private CaptureRequest previewRequest; // 定义CameraCaptureSession成员变量 private CameraCaptureSession captureSession; private ImageReader imageReader; private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture texture
, int width, int height) { // 当TextureView可用时,打开摄像头 openCamera(width, height);
} @Override public void onSurfaceTextureSizeChanged(SurfaceTexture texture
, int width, int height) {
} @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { return true;
} @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
}; private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { // 摄像头被打开时激发该方法 @Override public void onOpened(CameraDevice cameraDevice) {
MainActivity.this.cameraDevice = cameraDevice; // 开始预览 createCameraPreviewSession(); // ② } // 摄像头断开连接时激发该方法 @Override public void onDisconnected(CameraDevice cameraDevice) {
cameraDevice.close();
MainActivity.this.cameraDevice = null;
} // 打开摄像头出现错误时激发该方法 @Override public void onError(CameraDevice cameraDevice, int error) {
cameraDevice.close();
MainActivity.this.cameraDevice = null;
MainActivity.this.finish();
}
}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureView = (AutoFitTextureView) findViewById(R.id.texture); // 为该组件设置监听器 textureView.setSurfaceTextureListener(mSurfaceTextureListener);
findViewById(R.id.capture).setOnClickListener(this);
} @Override public void onClick(View view) {
captureStillPicture();
} private void captureStillPicture() { try { if (cameraDevice == null) { return;
} // 创建作为拍照的CaptureRequest.Builder final CaptureRequest.Builder captureRequestBuilder =
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); // 将imageReader的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
, ORIENTATIONS.get(rotation)); // 停止连续取景 captureSession.stopRepeating(); // 捕获静态图像 captureSession.capture(captureRequestBuilder.build()
, new CameraCaptureSession.CaptureCallback() // ⑤ { // 拍照完成时激发该方法 @Override public void onCaptureCompleted(CameraCaptureSession session
, CaptureRequest request, TotalCaptureResult result) { try { // 重设自动对焦模式 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); // 设置自动曝光模式 previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // 打开连续取景模式 captureSession.setRepeatingRequest(previewRequest, null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
} // 打开摄像头 private void openCamera(int width, int height) {
setUpCameraOutputs(width, height);
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { // 打开摄像头 if (ActivityCompat.checkSelfPermission(this, 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;
}
manager.openCamera(mCameraId, stateCallback, null); // ① } catch (CameraAccessException e) {
e.printStackTrace();
}
} private void createCameraPreviewSession() { try {
SurfaceTexture texture = textureView.getSurfaceTexture();
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
Surface surface = new Surface(texture); // 创建作为预览的CaptureRequest.Builder previewRequestBuilder = cameraDevice
.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 将textureView的surface作为CaptureRequest.Builder的目标 previewRequestBuilder.addTarget(new Surface(texture)); // 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求 cameraDevice.createCaptureSession(Arrays.asList(surface
, imageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③ { @Override public void onConfigured(CameraCaptureSession cameraCaptureSession) { // 如果摄像头为null,直接结束方法 if (null == cameraDevice) { return;
} // 当摄像头已经准备好时,开始显示预览 captureSession = cameraCaptureSession; try { // 设置自动对焦模式 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 设置自动曝光模式 previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // 开始显示相机预览 previewRequest = previewRequestBuilder.build(); // 设置预览时连续捕获图像数据 captureSession.setRepeatingRequest(previewRequest, null, null); // ④ } catch (CameraAccessException e) {
e.printStackTrace();
}
} @Override public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Toast.makeText(MainActivity.this, "配置失败!" , Toast.LENGTH_SHORT).show();
}
}, null );
} catch (CameraAccessException e) {
e.printStackTrace();
}
} private void setUpCameraOutputs(int width, int height) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { // 获取指定摄像头的特性 CameraCharacteristics characteristics
= manager.getCameraCharacteristics(mCameraId); // 获取摄像头支持的配置属性 StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // 获取摄像头支持的最大尺寸 Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea()); // 创建一个ImageReader对象,用于获取摄像头的图像数据 imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, 2);
imageReader.setOnImageAvailableListener( new ImageReader.OnImageAvailableListener() { // 当照片数据可用时激发该方法 @Override public void onImageAvailable(ImageReader reader) { // 获取捕获的照片数据 Image image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; // 使用IO流将照片写入指定文件 File file = new File(getExternalFilesDir(null), "pic.jpg");
buffer.get(bytes); try (
FileOutputStream output = new FileOutputStream(file)) {
output.write(bytes);
Toast.makeText(MainActivity.this, "保存: " + file, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
} finally {
image.close();
}
}
}, null); // 获取最佳的预览尺寸 previewSize = chooseOptimalSize(map.getOutputSizes(
SurfaceTexture.class), width, height, largest); // 根据选中的预览尺寸来调整预览组件(TextureView的)的长宽比 int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
textureView.setAspectRatio(
previewSize.getWidth(), previewSize.getHeight());
} else {
textureView.setAspectRatio(
previewSize.getHeight(), previewSize.getWidth());
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
Log.d(TAG, "出现错误");
}
} private static Size chooseOptimalSize(Size[] choices
, int width, int height, Size aspectRatio) { // 收集摄像头支持的打过预览Surface的分辨率 List bigEnough = new ArrayList<>(); int w = aspectRatio.getWidth(); int h = aspectRatio.getHeight(); for (Size option : choices) { if (option.getHeight() == option.getWidth() * h / w &&
option.getWidth() >= width && option.getHeight() >= height) {
bigEnough.add(option);
}
} // 如果找到多个预览尺寸,获取其中面积最小的。 if (bigEnough.size() > 0) { return Collections.min(bigEnough, new CompareSizesByArea());
} else {
System.out.println("找不到合适的预览尺寸!!!"); return choices[0];
}
} // 为Size定义一个比较器Comparator static class CompareSizesByArea implements Comparator { @Override public int compare(Size lhs, Size rhs) { // 强转为long保证不会发生溢出 return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
}
-
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
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
上面的程序中序号①的代码是用于打开系统摄像头,openCamera()方法的第一个参数代表请求打开的摄像头ID,此处传入的摄像头ID为 “0”,这代表打开设备后置摄像头;如果需要打开设备指定摄像头(比如前置摄像头),可以在调用openCamera()方法时传入相应的摄像头ID。
<uses-permission android:name="android.permission.CAMERA" />