一、基础知识
1、Google官网的Cam流程如下图1
2、Cam的预览、拍照、录像是分开的
Cam的预览、拍照、录像是各自独立的-换句话说可以不开启预览拍照或者录像–后面代码会详细介绍;市场上的成品Cam应用,打开Cam后直接打开了预览,然后可以拍照或者录视频是为了更好的排出效果;
之前电商能找出很多的针孔摄像头专门搞偷拍的-没有预览直接录视频或者拍照,由于出事太多”背后的灰色产业链!”,此类产品全部下架。
二、demo分析
1、权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera2.full" />
2、UI布局
- 设置一个TextureView 用于 预览图片;
- 设置一个ImageView 用于 显示拍照结果;
- 设置一个 Button 用于触发拍照功能;
这里介绍一下 为什么用TextureView,而不用SurfaceView、SurfaceTexture、GLSurfaceView或者其他view?
- View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢。
- SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面,所以刷新界面速度比view快。其缺点是不能做变形和动画,也不能随屏幕的变化而变化,另外不能在其上面覆盖其它的SurfaceView。
- GLSurfaceView:是surfaceview的子类,在其基础上封装了egl环境管理,以及render线程。专用于3D游戏开发的视图,OpenGL ES专用。
- TextureView:它也是继承自View,只能运行中硬件加速窗口。它的功能类似于SurfaceView + SurfaceTexture,它内部包含一个SurfaceTexture,它可以让Camera的数据和显示分离,比如需要做二次处理时,如Camera吧采集的数据发送给SurfaceTexture(比如做个美颜),SurfaceTexture处理后传给TextureView显示。TextureView可以做view的变形和动画。一般它是在主线程上做处理(在Android 5.0引入渲染线程后,它是在渲染线程中做的)。
- https://source.android.google.cn/devices/graphics/arch-st?hl=zh-cn
SurfaceTexture 是 Surface 和 OpenGL ES (GLES) 纹理的组合。SurfaceTexture 实例用于提供输出到 GLES 纹理的接口。
SurfaceTexture 包含一个以应用为使用方的 BufferQueue 实例。当生产方将新的缓冲区排入队列时,onFrameAvailable() 回调会通知应用。然后,应用调用 updateTexImage(),这会释放先前占用的缓冲区,从队列中获取新缓冲区并执行 EGL 调用,从而使 GLES 可将此缓冲区作为外部纹理使用。
3、代码
总体流程本来打算自己画的,偶然看到了这个
就直接拿来作参考了原图地址
1、 view初始化
textureView = findViewById(R.id.texture_view_camera2);
imageViewThumb = findViewById(R.id.thumb_iv);
findViewById(R.id.shutter_button).setOnClickListener(this);
2、获取CameraManger、给TextureView设置状态监听
cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
textureView.setSurfaceTextureListener(this);
3、获取当前机器支持的size的list 然后找到设定比例中的最大值 设置为默认size(一般都封装成util)
private int firstWidthRatio = 3;//画幅比例宽度比值分子
private int firstHeightRatio = 4;//画幅比例高度比值分母
private int firstDeviceWidth;//设备宽度
private int firstDeviceHeight;//设备高度
private Size previewSizeStandard;//预览尺寸标准
private Size photoSizeStandard;//照片尺寸标准
private void initPictureSize() {
try {
for (String camId : cameraManager.getCameraIdList()) {
cameraId = camId;
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//输出流配置
Size[] previewSizeList = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);//获取当前相机支持的所有尺寸列表
previewSizeStandard = SizeUtil.getSuitableSize(previewSizeList, firstWidthRatio, firstHeightRatio, firstDeviceWidth, firstDeviceHeight);
/*根据画幅比例和设备宽高在支持的所有尺寸列表中过滤出尺寸标准用作照片(ImageReader)(如果获取了多个照片尺寸,选择尺寸最大的(排序))(在设置ImageReader和缩略图的时候使用)(尚未生效)*/
Size[] photoSizeList = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);//获取兼容JPEG格式的所有尺寸列表
photoSizeStandard = SizeUtil.getSuitableSizePhoto(photoSizeList, firstWidthRatio, firstHeightRatio, firstDeviceWidth, firstDeviceHeight);
/*
* 获取到最佳预览尺寸的标准后,我们就可以修改自定义的TextureView的尺寸,避免画面拉伸的情况
*/
textureView.setAspectRation(previewSizeStandard.getHeight(), previewSizeStandard.getWidth());
Log.d(TAG, "previewSizeStandard:=" + previewSizeStandard);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/* 返回采样率InSampleSize(int)
* 不断将尺寸减半,直到小于缩略图的尺寸*/
public static int getInSampleSize(Size picSize, int reqHeight, int reqWidth) {
int height = picSize.getWidth();
int width = picSize.getHeight();
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
4、初始化ImageReader
private void initImageReader() {
imageReader = ImageReader.newInstance(photoSizeStandard.getWidth(), photoSizeStandard.getHeight(), ImageFormat.JPEG, 2);
imageReader.setOnImageAvailableListener(this, null);
Log.d(TAG, "setOnImageAvailableListener X");
captureSurface = imageReader.getSurface();
}
5、打开相机和各种回调
// 申请相机权限
private void checkPermission() {
Log.d(TAG, "checkPermission");
// 检查是否申请了权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
openCamera();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
}
}
}
private void openCamera() {
Log.d(TAG, "openCamera");
// 4 检查相机权限
checkPermission();
// 5 开启相机(传入:要开启的相机ID,和状态回调对象)
try {
initCamStateCallBack();
cameraManager.openCamera(cameraId, camStateCallback, mHandler);
Log.d(TAG, "openCamera X");
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void initCamStateCallBack() {
camStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
Log.d(TAG, "openCamera onOpened");
cameraDevice = camera;
try {
SurfaceTexture sTexture = textureView.getSurfaceTexture();
sTexture.setDefaultBufferSize(previewSizeStandard.getWidth(), previewSizeStandard.getHeight());
previewSurface = new Surface(sTexture);
Log.d(TAG, "previewSizeStandard:=" + previewSizeStandard);
// 2.2 构建请求对象(设置预览参数,和输出对象)
previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 设置参数:预览
previewRequestBuilder.addTarget(previewSurface); // 设置参数:目标容器
captureRequest = previewRequestBuilder.build();
CamCaptureSessionStateCallback();
// 2.3 创建会话
//如果些成cameraDevice.createCaptureSession(Arrays.asList(previewSurface), camCaptureSessionStateCallback, mHandler);虽然也不会报错,但不会走setOnImageAvailableListener的回调
cameraDevice.createCaptureSession(Arrays.asList(previewSurface, imageReader.getSurface()), camCaptureSessionStateCallback, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
Log.d(TAG, "openCamera onOpened");
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.d(TAG, "openCamera onError");
}
};
}
private void CamCaptureSessionStateCallback() {
camCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override //2.3.1 会话准备好了,在里面创建 预览或拍照请求
public void onConfigured(@NonNull CameraCaptureSession session) {
cameraCaptureSession = session;
//预览代码,上文中说到,可以不开启预览拍照,如果把此 session.setRepeatingRequest(captureRequest, null, null);注释掉就不会开启预览,且不影响后续拍照
// try {
// Log.d(TAG, "CamCaptureSessionStateCallback onConfigured");
// session.setRepeatingRequest(captureRequest, null, null);
// } catch (CameraAccessException e) {
// e.printStackTrace();
// }
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.d(TAG, "CamCaptureSessionStateCallback onConfigureFailed");
}
};
}
//**********************************TextureView.SurfaceTextureListener*****start*****************************
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
Log.d(TAG, "onSurfaceTextureAvailable ");
previewSurface = new Surface(textureView.getSurfaceTexture());
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
Log.d(TAG, "onSurfaceTextureSizeChanged ");
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
Log.d(TAG, "onSurfaceTextureDestroyed ");
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
// Log.d(TAG,"onSurfaceTextureUpdated ");
}
//**********************************TextureView.SurfaceTextureListener*****end*****************************
6、按快门拍照
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.shutter_button:
Log.d(TAG, "onClick shutter_button");
try {
configureCaptureBeforeCapture();
cameraCaptureSession.stopRepeating();
cameraCaptureSession.capture(captureRequestBuild.build(), captureCallback, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
break;
}
}
private void configureCaptureBeforeCapture() {
try {
captureRequestBuild = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
} catch (CameraAccessException e) {
e.printStackTrace();
}
captureRequestBuild.set(CaptureRequest.JPEG_ORIENTATION, 90);
captureRequestBuild.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
//B4.2 配置request的参数 的目标对象
SurfaceTexture sTexture = textureView.getSurfaceTexture();
sTexture.setDefaultBufferSize(previewSizeStandard.getWidth(), previewSizeStandard.getHeight());
captureSurface = new Surface(sTexture);
// captureRequestBuild.addTarget(captureSurface);//把capture的图片渲染到 TextureView 上 ,如果注释掉此demo不会把图片显示到 TextureView ,
// captureRequestBuild.addTarget(imageReader.getSurface());//ImageAvailableListener 回调(保存图片用的),注释掉不会走ImageAvailableListener的回调逻辑
}
@Override
public void onImageAvailable(ImageReader reader) {
Log.d(TAG, "setOnImageAvailableListener onImageAvailable");
Image image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
int length = buffer.remaining();
byte[] bytes = new byte[length];
buffer.get(bytes);
image.close();
BitmapFactory.Options sampler = new BitmapFactory.Options();
//设置采样率
sampler.inSampleSize = getInSampleSize(photoSizeStandard, 60, 60);//(缩略图尺寸px转换为int)
//将byte[]转换为bitmap
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, sampler);
//B2.2 显示图片
imageViewThumb.setImageBitmap(bitmap);
}
private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
Log.d(TAG, "onCaptureStarted");
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
super.onCaptureProgressed(session, request, partialResult);
Log.d(TAG, "onCaptureProgressed");
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
Log.d(TAG, "onCaptureCompleted");
//拍照完成后是否继续预览
// try {
// cameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(),null,null);
// } catch (CameraAccessException e) {
// e.printStackTrace();
// }
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
super.onCaptureFailed(session, request, failure);
Log.d(TAG, "onCaptureFailed");
}
@Override
public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, int sequenceId, long frameNumber) {
super.onCaptureSequenceCompleted(session, sequenceId, frameNumber);
Log.d(TAG, "onCaptureSequenceCompleted");
}
@Override
public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) {
super.onCaptureSequenceAborted(session, sequenceId);
Log.d(TAG, "onCaptureSequenceAborted");
}
@Override
public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) {
super.onCaptureBufferLost(session, request, target, frameNumber);
Log.d(TAG, "onCaptureBufferLost");
}
};
7、验证预览、拍照是独立的,
方式一:
正常预览时拍照:
05-12 11:43:30.011 8024 8024 D MainActivity: onClick shutter_button
05-12 11:43:30.363 8024 8054 D MainActivity: onCaptureStarted
05-12 11:43:30.376 8024 8054 D MainActivity: onCaptureCompleted
05-12 11:43:30.377 8024 8054 D MainActivity: onCaptureSequenceCompleted
05-12 11:43:30.400 8024 8024 D MainActivity: setOnImageAvailableListener onImageAvailable
当没有onImageAvailable 对调时 不设置 captureRequestBuild.addTarget(imageReader.getSurface());
:~$ alog "runtime|MainActi" -i
05-12 10:36:47.036 6848 6848 D MainActivity: onClick shutter_button
05-12 10:36:47.089 6848 6875 D MainActivity: onCaptureStarted
05-12 10:36:47.093 6848 6875 D MainActivity: onCaptureCompleted
05-12 10:36:47.093 6848 6875 D MainActivity: onCaptureSequenceCompleted
没有 MainActivity: setOnImageAvailableListener onImageAvailable