如今不少app
都会有拍照功能,通常调用系统进行拍照裁剪就能知足平时的需求,但有些场景或者特殊状况下如:持续不间断拍多张照片或者是进行人脸识别的时候,这时候之间调用系统原生相机拍照时不能知足本身的开发需求,就须要使用原生Camera
来进行自定义开发,本文会采用android.hardware.Camera
API来进行开发。在Android
生态中,Camera
是碎片化较为严重的一块,由于如今Android
自己有三套API:php
下面打算分别采用camera1
,camera2
,cameraX
来实现相机开发。java
另外各家厂商(华为,OPPO,VIVO,小米)都对Camera2
支持程度各不相同,从而致使须要花很大功夫来作适配工做,不少时候直接采用camera1
进行开发。 作过相机的同窗都知道,Camera1
相机开发通常分为五个步骤:android
了解完开发步骤后,由于本文是针对Camera1
来进行开发,那下面先了解一些具体的类和方法。git
Surface
根据英文直译是表面的意思,在源码中有这样描述的:github
/** * Handle onto a raw buffer that is being managed by the screen compositor. * * <p>A Surface is generally created by or from a consumer of image buffers (such as a * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL}, * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw * into.</p> */
复制代码
上面的意思:Surface
是用来处理屏幕显示内容合成器所管理的原始缓存区工具,它一般由图像缓冲区的消费者来建立如(SurfaceTexture,MediaRecorder),而后被移交给生产者(如:MediaPlayer)或者显示到其上(如:CameraDevice),从上面能够得知:面试
在Surface
内有一个Canvas
成员:canvas
private final Canvas mCanvas = new CompatibleCanvas();
复制代码
咱们知道,画图都是在Canvas
对象上来画的,由于Suface
持有Canvas
,那么咱们能够这样认为,Surface
是一个句柄,而Canvas
是开发者画图的场所,就像黑板,而原生缓冲器(rawbuffer)是用来保存数据的地方,全部获得Surface
就能获得其中的Canvas
和原生缓冲器等其余内容。数组
SurfaceView
简单理解就是Surface
的View。缓存
/** * Provides a dedicated drawing surface embedded inside of a view hierarchy. * You can control the format of this surface and, if you like, its size; the * SurfaceView takes care of placing the surface at the correct location on the * screen */
复制代码
意思就是
SurfaceView
提供了嵌入视图层级中的专用surface
,你能够控制surface
的格式或者大小(经过SurfaceView就能够看到Surface部分或者所有内容),SurfaceView
负责把surface
显示在屏幕的正确位置。网络
public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
....
final Surface mSurface = new Surface(); // Current surface in use
....
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder(){
.....
}
}
复制代码
SurfaceView
继承自View
,而且其中有两个成员变量,一个是Surface
对象,一个是SurfaceHolder
对象,SurfaceView
将Surface
显示在屏幕上,SurfaceView
经过SurfaceHolder
得知Surface
的状态(建立、变化、销毁),能够经过getHolder()
方法得到当前SurfaceView
的SurfaceHolder
对象,而后就能够对SurfaceHolder
对象添加回调来监听Surface
的状态。
Surface
是从Object
派生而来,实现了Parcelable
接口,看到Parcelable
很容易让人想到数据,而SurfaceView
就是用来展现Surface
数据的,二者的关系能够用下面一张图来描述:
到这里也许你们会有一个疑问,SurfaceView
和普通的View
有什么区别?相机开发就必定要用SurfaceView
吗?
首先普通的View
和其派生类都是共享同一个Surface
,全部的绘制必须在主线程(UI线程)进行,经过Surface
得到对应的Canvas
,完成绘制View
的工做。
SurfaceView
是特殊的View
,它不与其余普通的view
共享Surface
,在本身内部持有Surface
能够在独立的线程中进行绘制,在自定义相机预览图像这块,更新速度比较快和帧率要求比较高,若是用普通的View
去更新,极大可能会阻塞UI线程,SurfaceView
是在一个新起的线程去更新画面并不会阻塞UI线程。还有SurfaceView
底层实现了双缓冲机制,双缓冲技术主要是为了解决反复局部刷新带来的闪烁问题,对于像游戏,视频这些画面变化特别频繁,若是前面没有显示完,程序又从新绘制,这样会致使屏幕不停得闪烁,而双缓冲及时会把要处理的图片在内存中处理后,把要画的东西先画到一个内存区域里,而后总体一次行画处理,显示在屏幕上。举例说明: 在Android中,若是自定义View
大多数都会重写onDraw
方法,onDraw
方法并非绘制一点显示一点,而是绘制完成后一次性显示到屏幕上。由于CPU访问内存的速度远远大于访问屏幕的速度,若是须要绘制大量复杂的图形时,每次都一个个从内存读取图形而后绘制到屏幕就会形成屡次访问屏幕,这些效率会很低。为了解决这个问题,咱们能够建立一个临时的Canvas
对象,将图像都绘制到这个临时的Canvas
对象中,绘制完成后经过drawBitmap
方法绘制到onDraw
方法中的Canvas
对象中,这样就相对于Bitmap
的拷贝过程,比直接绘制效率要高。
因此相机开发中最适合用SurfaceView
来绘制。
/** * Abstract interface to someone holding a display surface. Allows you to * control the surface size and format, edit the pixels in the surface, and * monitor changes to the surface. This interface is typically available * through the {@link SurfaceView} class. * * <p>When using this interface from a thread other than the one running * its {@link SurfaceView}, you will want to carefully read the * methods * {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated()}. */
public interface SurfaceHolder {
....
public interface Callback {
public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
public void surfaceDestroyed(SurfaceHolder holder);
...
}
}
复制代码
这是一个抽象的接口给持有
surface
对象使用,容许你控制surface
的大小和格式,编辑surface
中的像素和监听surface
的变化,这个接口一般经过SurfaceView
这个类来得到。
另外SurfaceHolder
中有一个Callback
接口,这个接口有三个方法:
public void surfaceCreated(SurfaceHolder holder)
surface第一次建立回调
public void surfaceChanged(SurfaceHolder,int format,int width,int height)
surface变化的时候会回调
public void surfaceDestroyed(SurfaceHolder holder)
surface销毁的时候回调
除了上面Callback
接口比较重要外,另外还有如下几个方法也比较重要:
public void addCallback(Callback callback)
为SurfaceHolder添加回调接口
public void removeCallback(Callback callback)
对SurfaceHolder移除回调接口
public Canvas lockCanvas()
获取Canvas对象而且对它上锁
public Canvas lockCanvas(Rect dirty)
获取一个Canvas对象,而且对它上锁,可是所动的内容是dirty所指定的矩形区域
public void unlockCanvasAndPost(Canvas canvas)
当修改Surface中的数据完成后,释放同步锁,而且提交改变,将新的数据进行展现,同时Surface中的数据会丢失,加锁的目的就是为了在绘制的过程当中,Surface数据不会被改变。
public void setType(int type)
设置Surface的类型,类型有如下几种:
SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access)引擎和硬件加速的Surface
SURFACE_TYPE_GPU:适用于GPU加速的Surface
SURFACE_TYPE_PUSH_BUFFERS:代表该Surface不包含原生数据,Surface用到的数据由其余对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅,若是设置这种类型就不能调用lockCanvas来获取Canvas对象。
到这里,会发现Surface
、SurfaceView
和SurfaceHolder
就是典型的MVC模型。
上面三者的关系能够用下面一张图来表示:
查看源码时,发现android.hardware.camera
google不推荐使用了:
Camera
最主要的成员和一些接口:
在Camera
类里,CameraInfo
是静态内部类:
/** * Information about a camera * 用来描述相机信息 * @deprecated We recommend using the new {@link android.hardware.camera2} API for new * applications. * 推荐在新的应用使用{android.hardware.camera2}API */
@Deprecated
public static class CameraInfo {
/** * The facing of the camera is opposite to that of the screen. * 相机正面和屏幕正面相反,意思是后置摄像头 */
public static final int CAMERA_FACING_BACK = 0;
/** * The facing of the camera is the same as that of the screen. * 相机正面和屏幕正面一致,意思是前置摄像头 */
public static final int CAMERA_FACING_FRONT = 1;
/** * The direction that the camera faces. It should be * CAMERA_FACING_BACK or CAMERA_FACING_FRONT. * 摄像机面对的方向,它只能是CAMERA_FACING_BACK或者CAMERA_FACING_FRONT * */
public int facing;
/** * <p>The orientation of the camera image. The value is the angle that the * camera image needs to be rotated clockwise so it shows correctly on * the display in its natural orientation. It should be 0, 90, 180, or 270.</p> * orientation是相机收集图片的角度,这个值是相机采集的图片须要顺时针旋转才能正确显示自 * 然方向的图像,它必须是0,90,180,270中 * * * <p>For example, suppose a device has a naturally tall screen. The * back-facing camera sensor is mounted in landscape. You are looking at * the screen. If the top side of the camera sensor is aligned with the * right edge of the screen in natural orientation, the value should be * 90. If the top side of a front-facing camera sensor is aligned with * the right of the screen, the value should be 270.</p> * 举个例子:假设如今竖着拿着手机,后面摄像头传感器是横向(水平方向)的,你如今正在看屏幕 * 若是摄像机传感器的顶部在天然方向上右边,那么这个值是90度(手机是竖屏,传感器是横屏的)* * 若是前置摄像头的传感器顶部在手机屏幕的右边,那么这个值就是270度,也就是说这个值是相机图像顺时针 * 旋转到设备天然方向一致时的角度。 * */
public int orientation;
/** * <p>Whether the shutter sound can be disabled.</p> * 是否禁用开门声音 */
public boolean canDisableShutterSound;
};
复制代码
可能不少人对上面orientation
解释有点懵,这里重点讲一下orientation
,首先先知道四个方向:屏幕坐标方向,天然方向,图像传感器方向,相机预览方向。
每一个设备都有一个天然方向,手机和平板天然方向不同,在Android
应用程序中,android:screenOrientation
来控制activity
启动时的方向,默认值unspecified
即为天然方向,固然能够取值为:
默认的话:平板的天然方向是横屏,而手机的天然方向是竖屏方向。
手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,方向通常是和手机横屏方向一致,以下图:
将图像传感器捕获的图像,显示在屏幕上的方向。在默认状况下,和图像传感器方向一致,在相机API中能够经过setDisplayOrientation(int degrees)
设置预览方向(顺时针设置,不是逆时针)。默认状况下,这个值是0,在注释文档中:
/** * Set the clockwise rotation of preview display in degrees. This affects * the preview frames and the picture displayed after snapshot. This method * is useful for portrait mode applications. Note that preview display of * front-facing cameras is flipped horizontally before the rotation, that * is, the image is reflected along the central vertical axis of the camera * sensor. So the users can see themselves as looking into a mirror. * * <p>This does not affect the order of byte array passed in {@link * PreviewCallback#onPreviewFrame}, JPEG pictures, or recorded videos. This * method is not allowed to be called during preview. * * 设置预览显示的顺时针旋转角度,会影响预览帧和拍拍照后显示的图片,这个方法对竖屏模式的应用 * 颇有用,前置摄像头进行角度旋转以前,图像会进行一个水平的镜像翻转,用户在看预览图像的时候* 就像镜子同样了,这个不影响PreviewCallback的回调,生成JPEG图片和录像文件的方向。 * */
复制代码
注意,对于手机来讲:
(setDisplayOrientation(90))
,这样预览界面和实物方向一致。下面举个简单例子:
这里重点讲解一下竖屏下:
须要结合上下两张图来看:
在Android
中,对于前置摄像头,有如下规定:
同理这里重点讲一下,前置竖屏:
在前置相机中,预览图像和相机收集图像是镜像关系,上面图中Android
图标中前置收集图像和预览图像时相反的,前置相机图像传感器方向和前置相机预览图像方向是左右相反的,上图也有体现。
Camera
内的方法:facing表明相机方向,可取值有二:
是否禁用快门声音
PreviewCallback
是一个接口,能够给Camera
设置Camrea.PreviewCallback
,而且实现这个onPreviewFrame(byte[] data, Camera camera)
这个方法,就能够去Camera
预览图片时的数据,若是设置Camera.setPreviewCallback(callback)
,onPreviewFrame
这个方法会被一直调用,能够在摄像头对焦成功后设置camera.setOneShotPreviewCallback(previewCallback)
,这样设置onPreviewFrame
这个方法就会被调用异常,处理data数据,data是相机预览到的原始数据,能够保存下来当作一张照片。
AutoFocusCallback
是一个接口,用于在相机自动对焦完成后时通知回调,第一个参数是相机是否自动对焦成功,第二个参数是相机对象。
做为静态内部类,用来描述经过相机人脸检测识别的人脸信息。
是Rect
对象,表示检测到人脸的区域,这个Rect
对象中的坐标并非安卓屏幕中的坐标,须要进行转换才能使用。
人脸检测的置信度,范围是1到100。100是最高的信度
是一个Point
对象,表示的是检测到左眼的位置坐标
是一个Point
对象,表示的是检测到右眼的位置坐标
同时一个Point
对象,表示的是检测到嘴的位置坐标 leftEye
,rightEye
,mouth
有可能得到不到,并非全部相机支持,不支持状况下,获取为空。
表明拍照图片的大小。
拍照图片的宽
拍照图片的高
这是一个接口,当开始预览(人脸识别)的时候开始回调
通知监听器预览帧检测到的人脸,Face[]
是一个数组,用来存放检测的人脸(存放多张人脸),第二个参数是识别人脸的相机。
在Camera
做为内部类存在,是相机配置设置类,不一样设备可能具备不一样的照相机功能,如图片大小或者闪光模式。
设置预览相机图片的大小,width
是图片的宽,height
是图片的高
设置预览图片的格式,有如下格式:
设置保存图片的大小,width
图片的宽度,以像素为单位,height
是图片的高度,以像素为单位。
设置保存图片的格式,取值和setPreviewFormat
格式同样。
上面已经讲过,设置相机采集照片的角度,这个值是相机所采集的图片须要顺时针选择到天然方向的角度值,它必须是0,90,180或者270中的一个。
设置相机对焦模式,对焦模式有如下:
设置缩放系数,也就是日常所说的变焦。
返回相机支持的预览图片大小,返回值是一个List<Size>
数组,至少有一个元素。
返回获取相机支持的视频帧大小,能够经过MediaRecorder来使用。
返回相机支持的图片预览格式,全部相机都支持ImageFormat.NV21,返回是集合类型而且返回至少包含一个元素。
以集合的形式返回相机支持采集的图片大小,至少返回一个元素。
以集合的形式返回相机支持的图片(拍照后)格式,至少返回一个元素。
以集合的形式返回相机支持的对焦模式,至少返回一个元素。
返回相机所支持的最多人脸检测数,若是返回0,则说明制定类型的不支持人脸识别。若是手机摄像头支持最多的人脸检测个数是5个,当画面超出5我的脸数,仍是检测到5我的脸数。
返回当前缩放值,这个值的范围在0到getMaxZoom()之间。
返回当前设备可用的摄像头个数。
返回指定id所表示的摄像头信息,若是getNumberOfCameras()返回N,那么有效的id值为0~(N-1),通常手机至少有先后两个摄像头。
使用传入的id所表示的摄像头来建立Camera对象,若是这个id所表示的摄像头被其余应用程序打开调用此方法会跑出异常,当使用完相机后,必须调用release()来释放资源,不然它会保持锁定状态,不可用其余应用程序。
根据所传入的SurfaceHolder对象来设置实时预览。
根据传入的PreviewCallback对象来监听相机预览数据的回调,PreviewCallback再上面已经讲过。
根据传入的Parameters对象来设置当前相机的参数信息。
根据传入的Parameters对象来返回当前相机的参数信息
开始预览,在屏幕上绘制预览帧,若是没有调用setPreviewDisplay(SurfaceHolder)或者setPreviewTexture(SurfaceTexture)直接调用这个方法是没有任何效果的,若是启动预览失败,则会引起RuntimeException。
中止预览,中止绘制预览帧到屏幕,若是中止失败,会引起RuntimeException。
开始人脸识别,这个要调用startPreview以后调用,也就是在预览以后才能进行人脸识别,若是不支持人脸识别,调用此方法会抛出IllegalArgumentException。
中止人脸识别。
给人脸检测设置监听,以便提供预览帧。
断开而且释放相机对象资源。
设置相机预览画面旋转的角度,在刚开始讲述orientation的时候讲述角度问题,查看源码时,有如下注释:
public static void setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
//获取window(Activity)旋转方向
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
//计算图像所要旋转的角度
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
//调整图像旋转角度
camera.setDisplayOrientation(result);
}
复制代码
上面已经描述过在竖屏下,对于后置相机来说:
只须要旋转后置相机的orientation也就是90度便可和屏幕方向保持一致;
对于前置相机预览方向,相机预览的图像是相机采集到的图像镜像,因此旋转orientation 270-180=90度才和屏幕方向一致。 CameraInfo是实例化的相机类,info.orientation是相机对于屏幕天然方向(左上角坐标系)的旋转角度数。 那下面跟着官方适配方法走:
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); rotation是预览Window的旋转方向,对于手机而言,当在清单文件设置Activity的screenOrientation="portait"时,rotation=0,这时候没有旋转,当screenOrientation="landScape"时,rotation=1。
对于后置摄像头,手机竖屏显示时,预览图像旋转的角度:result=(90-0+360)%360=90;手机横屏显示时,预览图像旋转:result = (90-0+360)%360 = 0;
camera.setDisplayOrientation(int param)这个方法是图片输出后所旋转的角度数,旋转值能够是0,90,180,270。
注意: camera.setDisplayOrientation(int param)这个方法仅仅是修改相机的预览方向,不会影响到PreviewCallback回调、生成的JPEG图片和录像视频的方向,这些数据的方向会和图像Sensor方向一致。
须要申请拍照权限和外部存储权限:
<!--权限申请 相机-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--使用uses-feature指定须要相机资源-->
<uses-feature android:name="android.hardware.Camera"/>
<!--须要自动聚焦 -->
<uses-feature android:name="android.hardware.camera.autofocus"/>
<!--存储图片或者视频-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
复制代码
在onCreate
检查权限:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initBind();
initListener();
checkNeedPermissions();
}
复制代码
/** * 检测须要申请的权限 * */
private void checkNeedPermissions(){
//6.0以上须要动态申请权限 动态权限校验 Android 6.0 的 oppo & vivo 手机时,始终返回 权限已被容许 可是当真正用到该权限时,却又弹出权限申请框。
if (Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//多个权限一块儿申请
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
} else {
//已经所有申请 初始化相机资源
initCamera();
}
}else{
//6.0如下不用动态申请
initCamera();
}
}
复制代码
在onRequestPermissionsResult
处理回调:
/** * 动态处理申请权限的结果 * 用户点击赞成或者拒绝后触发 * * @param requestCode 请求码 * @param permissions 权限 * @param grantResults 结果码 */
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
//获取权限一一验证
if (grantResults.length > 1) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (grantResults[1] == PackageManager.PERMISSION_GRANTED) {
initCamera();
} else {
//拒绝就要强行跳转设置界面
Permissions.showPermissionsSettingDialog(this, permissions[1]);
}
} else {
//拒绝就要强行跳转设置界面
Permissions.showPermissionsSettingDialog(this, permissions[0]);
}
} else {
ToastUtil.showShortToast(this, "请从新尝试~");
}
break;
}
}
复制代码
/** * 调用系统相机 * */
private void goSystemCamera(){
//在根目录建立jpg文件
cameraSavePath = new File(Environment.getExternalStorageDirectory().getPath() + "/" + System.currentTimeMillis() +".jpg");
//指定跳到系统拍照
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//适配Android 7.0以上版本应用私有目录限制被访问
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
uri = FileProvider.getUriForFile(this, SystemUtil.getPackageName(getApplicationContext()) + ".fileprovider",cameraSavePath);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else{
//7.0如下
uri = Uri.fromFile(cameraSavePath);
}
//指定ACTION为MediaStore.EXTRA_OUTPUT
intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);
//请求码赋值为1
startActivityForResult(intent,1);
}
复制代码
在OnActivityResult(int requestCode,int resultCode,Intent data)
方法作处理:
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
String photoPath;
//处理拍照后返回的图片路径
if(requestCode == 1 && resultCode == RESULT_OK){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
photoPath = String.valueOf(cameraSavePath);
}else{
photoPath = uri.getEncodedPath();
}
Log.d("拍照返回图片的路径:",photoPath);
Glide.with(this).load(photoPath).apply(RequestOptions.noTransformation()
.override(iv_photo.getWidth(),iv_photo.getHeight())
.error(R.drawable.default_person_icon))
.into(iv_photo);
}else if(requestCode == 2 && resultCode == RESULT_OK){
//处理调用相册返回的路径
photoPath = PhotoAlbumUtil.getRealPathFromUri(this,data.getData());
Glide.with(this).load(photoPath).apply(RequestOptions.noTransformation()
.override(iv_photo.getWidth(),iv_photo.getHeight())
.error(R.drawable.default_person_icon))
.into(iv_photo);
}
super.onActivityResult(requestCode, resultCode, data);
}
复制代码
下面按照如下步骤来实现自定义相机开发:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent">
<SurfaceView android:id="@+id/sf_camera" android:layout_width="match_parent" android:layout_height="match_parent"/>
<android.support.constraint.ConstraintLayout android:id="@+id/cl_bottom" android:layout_width="match_parent" android:layout_height="80dp" app:layout_constraintBottom_toBottomOf="parent" >
<!-- 拍照后显示的图片-->
<ImageView android:id="@+id/iv_photo" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginLeft="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" />
<!-- 拍照按钮-->
<TextView android:id="@+id/tv_takephoto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/icon_take_photo_selector" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
复制代码
布局文件主要有拍照预览控件SurfaceView
、拍照后显示的图片Imageview
、拍照按钮Textview
组成。
新增相机业务逻辑类CameraPresenter
,目的是将业务和界面显示分开,Activity
负责UI的显示,业务逻辑在CameraPresenter
,新增构造函数,构造函数有两个参数,分别是持有手机界面的Activity
和SurfaceView
对象,并根据传入的SurfaceView
对象经过SurfaceView.getHolder方法获取SurfaceHolder
对象:
public CameraPresenter(AppCompatActivity mAppCompatActivity, SurfaceView mSurfaceView) {
this.mAppCompatActivity = mAppCompatActivity;
this.mSurfaceView = mSurfaceView;
mSurfaceHolder = mSurfaceView.getHolder();
}
复制代码
SurfaceHolder对象设置监听回调:
public CameraPresenter(AppCompatActivity mAppCompatActivity, SurfaceView mSurfaceView) {
this.mAppCompatActivity = mAppCompatActivity;
this.mSurfaceView = mSurfaceView;
mSurfaceHolder = mSurfaceView.getHolder();
init();
}
/** * 初始化增长回调 */
private void init() {
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
//surface建立时执行
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//surface绘制时执行
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//surface销毁时执行
}
});
}
复制代码
在surfaceCreated(SurfaceHolder holder)
方法里调用打开相机:
//摄像头Id 默认后置 0,前置的值是1
private int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
@Override
public void surfaceCreated(SurfaceHolder holder) {
//surface建立时执行
if (mCamera == null) {
// mCameraId是后置仍是前置 0是后置 1是前置
openCamera(mCameraId);
}
}
/** * 打开相机 而且判断是否支持该摄像头 * * @param FaceOrBack 前置仍是后置 * @return */
private boolean openCamera(int FaceOrBack) {
//是否支持先后摄像头
boolean isSupportCamera = isSupport(FaceOrBack);
//若是支持
if (isSupportCamera) {
try {
mCamera = Camera.open(FaceOrBack);
} catch (Exception e) {
e.printStackTrace();
ToastUtil.showShortToast(mAppCompatActivity, "打开相机失败~");
return false;
}
}
return isSupportCamera;
}
复制代码
调用Camera.open(int cameraId)
后返回具体的Camera对象后,还须要设置相机一些参数,如预览模式,对焦模式等:
/** * 打开相机 而且判断是否支持该摄像头 * * @param FaceOrBack 前置仍是后置 * @return */
private boolean openCamera(int FaceOrBack) {
//是否支持先后摄像头
boolean isSupportCamera = isSupport(FaceOrBack);
//若是支持
if (isSupportCamera) {
try {
mCamera = Camera.open(FaceOrBack);
initParameters(mCamera);
//设置预览回调
if (mCamera != null) {
mCamera.setPreviewCallback(this);
}
} catch (Exception e) {
e.printStackTrace();
ToastUtil.showShortToast(mAppCompatActivity, "打开相机失败~");
return false;
}
}
return isSupportCamera;
}
/** * 设置相机参数 * * @param camera */
private void initParameters(Camera camera) {
try {
//获取Parameters对象
mParameters = camera.getParameters();
//设置预览格式
mParameters.setPreviewFormat(ImageFormat.NV21);
//判断是否支持连续自动对焦图像
if (isSupportFocus(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
//判断是否支持单次自动对焦
} else if (isSupportFocus(Camera.Parameters.FOCUS_MODE_AUTO)) {
//自动对焦(单次)
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
//给相机设置参数
mCamera.setParameters(mParameters);
} catch (Exception e) {
e.printStackTrace();
ToastUtil.showShortToast(mAppCompatActivity, "初始化相机失败");
}
复制代码
设置完相机参数以后,就能够须要相机调用Camera.setPreviewDisplay(SurfaceHolder holder)
和Camera.startPreview()
开启预览:
/** * 开始预览 */
private void startPreview() {
try {
//根据所传入的SurfaceHolder对象来设置实时预览
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.startPreview();
//这里同时开启人脸检测
startFaceDetect();
} catch (IOException e) {
e.printStackTrace();
}
}
/** * 人脸检测 */
private void startFaceDetect() {
//开始人脸识别,这个要调用startPreview以后调用
mCamera.startFaceDetection();
//添加回调
mCamera.setFaceDetectionListener(new Camera.FaceDetectionListener() {
@Override
public void onFaceDetection(Camera.Face[] faces, Camera camera) {
mCameraCallBack.onFaceDetect(transForm(faces), camera);
Log.d("sssd", "检测到" + faces.length + "人脸");
}
});
}
复制代码
在surfaceCreated(SurfaceHolder holder)
回调方法调用:
...
@Override
public void surfaceCreated(SurfaceHolder holder) {
//surface建立时执行
if (mCamera == null) {
//mCameraId是后置仍是前置 0是后置 1是前置
openCamera(mCameraId);
}
//并设置预览
startPreview();
}
...
复制代码
当相机再也不调用的时候,须要调用Camera.release()
来释放相机资源
/** * 释放相机资源 */
public void releaseCamera() {
if (mCamera != null) {
//中止预览
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
//释放相机资源
mCamera.release();
mCamera = null;
}
}
复制代码
在surfaceDestroyed(SurfaceHolder holder)
调用:
/** * 初始化增长回调 */
private void init() {
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
//surface建立时执行 mCameraId是后置仍是前置 0是后置 1是前置
if (mCamera == null) {
openCamera(mCameraId);
}
//并设置预览
startPreview();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//surface绘制时执行
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//surface销毁时执行
releaseCamera();
}
});
}
/** * 设置前置仍是后置 * * @param mCameraId 前置仍是后置 */
public void setFrontOrBack(int mCameraId) {
this.mCameraId = mCameraId;
}
复制代码
在自定义相机的Activity
界面进行调用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_customcamera);
//绑定View
initBind();
//添加点击,触摸事件等监听
initListener();
//初始化CameraPresenter
mCameraPresenter = new CameraPresenter(this,sf_camera);
//设置后置摄像头
mCameraPresenter.setFrontOrBack(Camera.CameraInfo.CAMERA_FACING_BACK);
}
复制代码
在onDestroy()
方法调用releaseCamera()
:
/** * Activity 销毁回调方法 释放各类资源 */
@Override
protected void onDestroy(){
super.onDestroy();
if(mCameraPresenter != null){
mCameraPresenter.releaseCamera();
}
}
复制代码
如今先看看效果:
发现预览效果图逆时针旋转了90度,当你把手机横屏摆放也是,上面已经说过,由于屏幕天然方向和图像传感器方向不一致形成的,须要从新设置预览时的角度,采用官方的推荐方法:
/** * 保证预览方向正确 * * @param appCompatActivity Activity * @param cameraId 相机Id * @param camera 相机 */
private void setCameraDisplayOrientation(AppCompatActivity appCompatActivity, int cameraId, Camera camera) {
Camera.CameraInfo info =
new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
//rotation是预览Window的旋转方向,对于手机而言,当在清单文件设置Activity的screenOrientation="portait"时,
//rotation=0,这时候没有旋转,当screenOrientation="landScape"时,rotation=1。
int rotation = appCompatActivity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
//计算图像所要旋转的角度
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
orientation = result;
//调整预览图像旋转角度
camera.setDisplayOrientation(result);
}
复制代码
并在startPreview()
方法里调用:
/** * 开始预览 */
private void startPreview() {
try {
//根据所传入的SurfaceHolder对象来设置实时预览
mCamera.setPreviewDisplay(mSurfaceHolder);
//调整预览角度
setCameraDisplayOrientation(mAppCompatActivity,mCameraId,mCamera);
mCamera.startPreview();
startFaceDetect();
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
再次看下运行效果:
上面调整了预览角度的问题后,由于在市面上安卓机型五花八门,屏幕分辨率也不少,为了不图像变形,须要调整预览图像和保存的图像尺寸:
//获取屏幕宽和高
private int screenWidth, screenHeight;
public CameraPresenter(AppCompatActivity mAppCompatActivity, SurfaceView mSurfaceView) {
this.mAppCompatActivity = mAppCompatActivity;
this.mSurfaceView = mSurfaceView;
mSurfaceHolder = mSurfaceView.getHolder();
DisplayMetrics dm = new DisplayMetrics();
mAppCompatActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
//获取宽高像素
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
Log.d("sssd-手机宽高尺寸:",screenWidth +"*"+screenHeight);
init();
}
/** * * 设置保存图片的尺寸 */
private void setPictureSize() {
List<Camera.Size> localSizes = mParameters.getSupportedPictureSizes();
Camera.Size biggestSize = null;
Camera.Size fitSize = null;// 优先选预览界面的尺寸
Camera.Size previewSize = mParameters.getPreviewSize();//获取预览界面尺寸
float previewSizeScale = 0;
if (previewSize != null) {
previewSizeScale = previewSize.width / (float) previewSize.height;
}
if (localSizes != null) {
int cameraSizeLength = localSizes.size();
for (int n = 0; n < cameraSizeLength; n++) {
Camera.Size size = localSizes.get(n);
if (biggestSize == null) {
biggestSize = size;
} else if (size.width >= biggestSize.width && size.height >= biggestSize.height) {
biggestSize = size;
}
// 选出与预览界面等比的最高分辨率
if (previewSizeScale > 0
&& size.width >= previewSize.width && size.height >= previewSize.height) {
float sizeScale = size.width / (float) size.height;
if (sizeScale == previewSizeScale) {
if (fitSize == null) {
fitSize = size;
} else if (size.width >= fitSize.width && size.height >= fitSize.height) {
fitSize = size;
}
}
}
}
// 若是没有选出fitSize, 那么最大的Size就是FitSize
if (fitSize == null) {
fitSize = biggestSize;
}
mParameters.setPictureSize(fitSize.width, fitSize.height);
}
}
/** * 设置预览界面尺寸 */
private void setPreviewSize() {
//获取系统支持预览大小
List<Camera.Size> localSizes = mParameters.getSupportedPreviewSizes();
Camera.Size biggestSize = null;//最大分辨率
Camera.Size fitSize = null;// 优先选屏幕分辨率
Camera.Size targetSize = null;// 没有屏幕分辨率就取跟屏幕分辨率相近(大)的size
Camera.Size targetSiz2 = null;// 没有屏幕分辨率就取跟屏幕分辨率相近(小)的size
if (localSizes != null) {
int cameraSizeLength = localSizes.size();
for (int n = 0; n < cameraSizeLength; n++) {
Camera.Size size = localSizes.get(n);
Log.d("sssd-系统支持的尺寸:",size.width + "*" +size.height);
if (biggestSize == null ||
(size.width >= biggestSize.width && size.height >= biggestSize.height)) {
biggestSize = size;
}
//若是支持的比例都等于所获取到的宽高
if (size.width == screenHeight
&& size.height == screenWidth) {
fitSize = size;
//若是任一宽或者高等于所支持的尺寸
} else if (size.width == screenHeight
|| size.height == screenWidth) {
if (targetSize == null) {
targetSize = size;
//若是上面条件都不成立 若是任一宽高小于所支持的尺寸
} else if (size.width < screenHeight
|| size.height < screenWidth) {
targetSiz2 = size;
}
}
}
if (fitSize == null) {
fitSize = targetSize;
}
if (fitSize == null) {
fitSize = targetSiz2;
}
if (fitSize == null) {
fitSize = biggestSize;
}
Log.d("sssd-最佳预览尺寸:",fitSize.width + "*" + fitSize.height);
mParameters.setPreviewSize(fitSize.width, fitSize.height);
}
}
复制代码
这里额外要注意:对于相机来讲,都是width是长边,也就是width > height,在上面setPreviewSize()
方法里,获取所支持的size.width
要和screenHeight
比较,size.height
要和screenWidth
,最后在设置相机里调用便可:
/** * 设置相机参数 * * @param camera */
private void initParameters(Camera camera) {
try {
//获取Parameters对象
mParameters = camera.getParameters();
//设置预览格式
mParameters.setPreviewFormat(ImageFormat.NV21);
setPreviewSize();
setPictureSize();
//.....
mCamera.setParameters(mParameters);
} catch (Exception e) {
e.printStackTrace();
ToastUtil.showShortToast(mAppCompatActivity, "初始化相机失败");
}
}
复制代码
下面看看在vivo x9所支持的尺寸:
下面进行拍照处理,拍照保存图片有两种方式:
Camera.takePicture(ShutterCallback shutter,PictureCallback raw,PictureCallback jpeg)
/** * Equivalent to <pre>takePicture(Shutter, raw, null, jpeg)</pre>. * * @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback) */
public final void takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg) {
takePicture(shutter, raw, null, jpeg);
}
/** * @param shutter the callback for image capture moment, or null * @param raw the callback for raw (uncompressed) image data, or null * @param postview callback with postview image data, may be null * @param jpeg the callback for JPEG image data, or null * @throws RuntimeException if starting picture capture fails; usually this * would be because of a hardware or other low-level error, or because * release() has been called on this Camera instance. */
public final void takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback postview, PictureCallback jpeg) {
...
}
复制代码
三个参数的takePicture
实际调用四个参数的takePicture
,只是带有postview图像数据的回调,设置为空了。
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
});
复制代码
在onPreviewFrame
以字节数组形式返回具体照片数据,这个方法会不停的回调,这里不演示这个方法,保存图片的方法和第一个方法是同样的。 首先先自定义回调:
//自定义回调
private CameraCallBack mCameraCallBack;
public interface CameraCallBack {
//预览帧回调
void onPreviewFrame(byte[] data, Camera camera);
//拍照回调
void onTakePicture(byte[] data, Camera Camera);
//人脸检测回调
void onFaceDetect(ArrayList<RectF> rectFArrayList, Camera camera);
//拍照路径返回
void getPhotoFile(String imagePath);
}
复制代码
调用Camera.takePicture
方法:
/** * 拍照 */
public void takePicture() {
if (mCamera != null) {
//拍照回调 点击拍照时回调 写一个空实现
mCamera.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
}, new Camera.PictureCallback() {
//回调没压缩的原始数据
@Override
public void onPictureTaken(byte[] data, Camera camera) {
}
}, new Camera.PictureCallback() {
//回调图片数据 点击拍照后相机返回的照片byte数组,照片数据
@Override
public void onPictureTaken(byte[] data, Camera camera) {
//拍照后记得调用预览方法,否则会停在拍照图像的界面
mCamera.startPreview();
//回调
mCameraCallBack.onTakePicture(data, camera);
//保存图片
getPhotoPath(data);
}
});
}
}
复制代码
保存图片目录先放在app内:
public class Configuration {
//这是app内部存储 格式以下 /data/data/包名/xxx/
public static String insidePath = "/data/data/com.knight.cameraone/pic/";
//外部路径
public static String OUTPATH = Environment.getExternalStorageDirectory() + "/拍照-相册/";
}
复制代码
建立目录具体方法:
/** * 建立拍照照片文件夹 */
private void setUpFile() {
photosFile = new File(Configuration.insidePath);
if (!photosFile.exists() || !photosFile.isDirectory()) {
boolean isSuccess = false;
try {
isSuccess = photosFile.mkdirs();
} catch (Exception e) {
ToastUtil.showShortToast(mAppCompatActivity, "建立存放目录失败,请检查磁盘空间~");
mAppCompatActivity.finish();
} finally {
if (!isSuccess) {
ToastUtil.showShortToast(mAppCompatActivity, "建立存放目录失败,请检查磁盘空间~");
mAppCompatActivity.finish();
}
}
}
}
复制代码
在初始化相机时先调用建立文件:
public CameraPresenter(AppCompatActivity mAppCompatActivity, SurfaceView mSurfaceView) {
//...
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
Log.d("sssd-手机宽高尺寸:",screenWidth +"*"+screenHeight);
//建立文件夹目录
setUpFile();
init();
}
复制代码
拍照后保存图片这种输出耗时操做应该用线程来处理,新建线程池类:
public class ThreadPoolUtil {
private static ExecutorService threadPool = Executors.newCachedThreadPool();
/** * 在线程池执行一个任务 * @param runnable 任务 */
public static void execute(Runnable runnable){
threadPool.execute(runnable);
}
}
复制代码
getPhotoPath(byte[] data)
方法:
/** * @return 返回路径 */
private void getPhotoPath(final byte[] data) {
ThreadPoolUtil.execute(new Runnable() {
@Override
public void run() {
long timeMillis = System.currentTimeMillis();
String time = SystemUtil.formatTime(timeMillis);
//拍照数量+1
photoNum++;
//图片名字
String name = SystemUtil.formatTime(timeMillis, SystemUtil.formatTime(photoNum) + ".jpg");
//建立具体文件
File file = new File(photosFile, name);
if (!file.exists()) {
try {
file.createNewFile();
} catch (Exception e) {
e.printStackTrace();
return;
}
}
try {
FileOutputStream fos = new FileOutputStream(file);
try {
//将数据写入文件
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//将图片保存到手机相册中
SystemUtil.saveAlbum(Configuration.insidePath + file.getName(), file.getName(), mAppCompatActivity);
//将图片复制到外部
SystemUtil.coptPicture(Configuration.insidePath + file.getName(),Configuration.OUTPATH,file.getName());
//发消息给主线程
Message message = new Message();
message.what = 1;
//文件路径
message.obj = Configuration.insidePath + file.getName();
mHandler.sendMessage(message);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
});
}
复制代码
上面代码先把照片存到app包内,再将照片复制到app包外,当图片保存处理完后,回调主线程进行显示图片:
@SuppressLint("HandlerLeak")
Handler mHandler = new Handler(){
@SuppressLint("NewApi")
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
mCameraCallBack.getPhotoFile(msg.obj.toString());
break;
default:
break;
}
}
};
复制代码
在Activity
中设置回调:
//添加监听
mCameraPresenter.setCameraCallBack(this);
复制代码
拍照后保存图片后显示在界面上,Activity
实现照片显示:
/** * 返回拍照后的照片 * @param imagePath */
@Override
public void getPhotoFile(String imagePath) {
//设置头像
Glide.with(this).load(imagePath)
.apply(RequestOptions.bitmapTransform(new CircleCrop())
.override(iv_photo.getWidth(), iv_photo.getHeight())
.error(R.drawable.default_person_icon))
.into(iv_photo);
}
复制代码
布局文件增长ImageView
来显示拍照存储后的图片:
<android.support.constraint.ConstraintLayout android:id="@+id/cl_bottom" android:layout_width="match_parent" android:layout_height="80dp" app:layout_constraintBottom_toBottomOf="parent" >
<!-- 拍照后显示的图片-->
<ImageView android:id="@+id/iv_photo" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginLeft="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" />
<!-- 拍照按钮-->
<TextView android:id="@+id/tv_takephoto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/icon_take_photo_selector" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
复制代码
效果以下:
发现拍照后存储的照片通过逆时针90度旋转,须要将顺时针90度,缘由在上面分析orientation的时候讲述过,虽然调整来预览图像角度,可是并不能调整图片传感器的图片方向,因此只能保存图片后再将图片旋转:
/** * 旋转图片 * @param cameraId 前置仍是后置 * @param orientation 拍照时传感器方向 * @param path 图片路径 */
private void rotateImageView(int cameraId,int orientation,String path){
Bitmap bitmap = BitmapFactory.decodeFile(path);
Matrix matrix = new Matrix();
//0是后置
if(cameraId == 0){
if(orientation == 90){
matrix.postRotate(90);
}
}
//1是前置
if(cameraId == 1){
//顺时针旋转270度
matrix.postRotate(270);
}
// 建立新的图片
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
File file = new File(path);
//从新写入文件
try{
// 写入文件
FileOutputStream fos;
fos = new FileOutputStream(file);
//默认jpg
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
resizedBitmap.recycle();
}catch (Exception e){
e.printStackTrace();
return;
}
}
复制代码
在保存图像后调用:
/** * * 返回图片路径 * @param data */
private void getPhotoPath(final byte[] data) {
...
//将图片旋转
rotateImageView(mCameraId,orientation,Configuration.insidePath + file.getName());
//将图片保存到手机相册
SystemUtil.saveAlbum(Configuration.insidePath + file.getName(), file.getName(), mAppCompatActivity);
...
}
复制代码
在布局文件添加TextView
做为先后摄像头转换:
<SurfaceView android:id="@+id/sf_camera" android:layout_width="match_parent" android:layout_height="match_parent"/>
<TextView android:id="@+id/tv_change_camera" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginRight="15dp" android:layout_marginTop="15dp" android:background="@drawable/icon_change_camera_default" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />
复制代码
在CameraPersenter
中,添加改变摄像头方法:
/** * 先后摄像切换 */
public void switchCamera() {
//先释放资源
releaseCamera();
//在Android P以前 Android设备仍然最多只有先后两个摄像头,在Android p后支持多个摄像头 用户想打开哪一个就打开哪一个
mCameraId = (mCameraId + 1) % Camera.getNumberOfCameras();
//打开摄像头
openCamera(mCameraId);
//切换摄像头以后开启预览
startPreview();
}
复制代码
具体调用:
case R.id.tv_change_camera:
mCameraPresenter.switchCamera();
break;
复制代码
效果以下图:
setDisplayOrientation()
设置预览方向,并无作镜面翻转,为何切换前置时,预览效果跟实物同样呢,原来是在调用
setDisplayOrientation()
作了水平镜面的翻转,可是拍照后保存下来的照片是没有水平翻转的,因此同时要对拍照后的照片作水平方向镜面翻转,那就在旋转图片里的方法加上翻转处理:
/** * 旋转图片 * @param cameraId 前置仍是后置 * @param orientation 拍照时传感器方向 * @param path 图片路径 */
private void rotateImageView(int cameraId,int orientation,String path){
Bitmap bitmap = BitmapFactory.decodeFile(path);
Matrix matrix = new Matrix();
// 建立新的图片
Bitmap resizedBitmap;
//0是后置
if(cameraId == 0){
if(orientation == 90){
matrix.postRotate(90);
}
}
//1是前置
if(cameraId == 1){
matrix.postRotate(270);
}
// 建立新的图片
resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
//新增 若是是前置 须要镜面翻转处理
if(cameraId == 1){
Matrix matrix1 = new Matrix();
matrix1.postScale(-1f,1f);
resizedBitmap = Bitmap.createBitmap(resizedBitmap, 0, 0,
resizedBitmap.getWidth(), resizedBitmap.getHeight(), matrix1, true);
}
File file = new File(path);
//从新写入文件
try{
// 写入文件
FileOutputStream fos;
fos = new FileOutputStream(file);
//默认jpg
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
resizedBitmap.recycle();
}catch (Exception e){
e.printStackTrace();
return;
}
}
复制代码
这样就能保证预览和拍摄后保存的照片和实物同样了。
拍照必不可少的一个功能:改变焦距。在Camera
中的内部类Camera.Parameters
有Parameters.setZoom(int value)
来调整预览图像缩放系数,由于在布局SurfaceView
是全屏的,在OnTouch
方法作处理,并点击屏幕进行自动变焦处理:
//默认状态
private static final int MODE_INIT = 0;
//两个触摸点触摸屏幕状态
private static final int MODE_ZOOM = 1;
//标识模式
private int mode = MODE_INIT;
...
/** * * 触摸回调 * @param v 添加Touch事件具体的view * @param event 具体事件 * @return */
@Override
public boolean onTouch(View v, MotionEvent event) {
//不管多少跟手指加进来,都是MotionEvent.ACTION_DWON MotionEvent.ACTION_POINTER_DOWN
//MotionEvent.ACTION_MOVE:
switch (event.getAction() & MotionEvent.ACTION_MASK){
//手指按下屏幕
case MotionEvent.ACTION_DOWN:
mode = MODE_INIT;
break;
//当屏幕上已经有触摸点按下的状态的时候,再有新的触摸点被按下时会触发
case MotionEvent.ACTION_POINTER_DOWN:
mode = MODE_ZOOM;
//计算两个手指的距离 两点的距离
startDis = SystemUtil.twoPointDistance(event);
break;
//移动的时候回调
case MotionEvent.ACTION_MOVE:
isMove = true;
//这里主要判断有两个触摸点的时候才触发
if(mode == MODE_ZOOM){
//只有两个点同时触屏才执行
if(event.getPointerCount() < 2){
return true;
}
//获取结束的距离
float endDis = SystemUtil.twoPointDistance(event);
//每变化10f zoom变1
int scale = (int) ((endDis - startDis) / 10f);
if(scale >= 1 || scale <= -1){
int zoom = mCameraPresenter.getZoom() + scale;
//判断zoom是否超出变焦距离
if(zoom > mCameraPresenter.getMaxZoom()){
zoom = mCameraPresenter.getMaxZoom();
}
//若是系数小于0
if(zoom < 0 ){
zoom = 0;
}
//设置焦距
mCameraPresenter.setZoom(zoom);
//将最后一次的距离设为当前距离
startDis = endDis;
}
}
break;
case MotionEvent.ACTION_UP:
//判断是否点击屏幕 若是是自动聚焦
if(isMove == false){
//自动聚焦
mCameraPresenter.autoFoucus();
}
isMove = false;
break;
}
return true;
}
复制代码
在CameraPresenter
内调用:
/** * 变焦 * @param zoom 缩放系数 */
public void setZoom(int zoom){
if(mCamera == null){
return;
}
//获取Paramters对象
Camera.Parameters parameters;
parameters = mCamera.getParameters();
//若是不支持变焦
if(!parameters.isZoomSupported()){
return;
}
//
parameters.setZoom(zoom);
//Camera对象从新设置Paramters对象参数
mCamera.setParameters(parameters);
mZoom = zoom;
}
/** * 自动变焦 */
public void autoFoucus(){
if(mCamera == null){
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
}
});
}
}
复制代码
最终效果以下图:
经过Parameters.setFlashMode(String value)
来控制闪光灯,参数类型有如下:
在平时中,用FLASH_MODE_OFF
和FLASH_MODE_TORCH
就行
/** * * 闪光灯 * @param turnSwitch true 为开启 false 为关闭 */
public void turnLight(boolean turnSwitch){
if(mCamera == null){
return;
}
Camera.Parameters parameters = mCamera.getParameters();
if(parameters == null){
return;
}
parameters.setFlashMode(turnSwitch ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
mCamera.setParameters(parameters);
}
复制代码
具体调用:
@Override
public void onClick(View v) {
switch (v.getId()){
//拍照
case R.id.iv_photo:
cy_photo.setVisibility(cy_photo.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
break;
//改变摄像头
case R.id.tv_change_camera:
mCameraPresenter.switchCamera();
break;
//关闭仍是开启闪光灯
case R.id.tv_flash:
mCameraPresenter.turnLight(isTurn);
tv_flash.setBackgroundResource(isTurn ? R.drawable.icon_turnon : R.drawable.icon_turnoff);
isTurn = !isTurn;
default:
break;
}
}
复制代码
实际效果:
到这里能够发现,相比于调用系统拍照的清晰度,自定义拍照就逊色一筹,感受上面有一层蒙版罩着。调用系统拍照能够发现,屏幕亮度故意调亮,那么是否是把自定义拍照的界面亮度调大,效果清晰度会不会好一些呢,下面试试,在CustomCameraActivity
加入:
/** * * 加入调整亮度 */
private void getScreenBrightness(){
WindowManager.LayoutParams lp = getWindow().getAttributes();
//screenBrightness的值是0.0-1.0 从0到1.0 亮度逐渐增大 若是是-1,那就是跟随系统亮度 这里调成 0.78左右
lp.screenBrightness = Float.valueOf(200) * (1f / 255f);
getWindow().setAttributes(lp);
}
复制代码
在onCreate
调用便可,最后效果以下:
自定义相机效果以下:
下面简单实现录制视频的功能,利用MediaRecorder
来实现直接录制视频,这里要注意:MediaRecorder是不能对每一帧数据作处理的,录制视频须要用到如下工具:
MediaRecorder是Android中面向应用层的封装,用于提供音视频编码的封装操做的工具,下面直接上官方图:
Initial
:在MediaRecorder
对象被建立时或者调用reset()
方法后,会处于该状态。Initialized
:当调用setAudioSource()
或者setVideoSource()
后就会处于该状态,这两个方法主要用于设置音视频的播放源配置,在该状态下能够调用reset()
回到Initial
状态。DataSourceConfigured
:当调用setOutputFormat
方法后,就会处于该状态,这个方法用来设置文件格式,如设置为mp4
或者mp3
,在这个状态同时能够设置音视频的封装格式,采样率,视频码率,帧率等,能够经过调用reset()
回到Initial
状态。Prepared
:当调用上面几个方法后,就能够调用prepare()
进入这个状态,只有处于这个状态才能调用start()
方法。Recording
:经过调用start()
来进入该状态,处于这个状态就是真正录制音视频,经过调用reset()
或者stop()
来回到Initial
状态。error
:当录制过程当中发生错误,就会进入该状态,调用reset()
回到Initial
状态。release
:释放系统资源,只有在Initial
状态才能调用release()
回到该状态。注意:要添加录音权限,这里不在讲述。
/** * 获取输出视频的width和height * */
public void getVideoSize(){
int biggest_width=0 ,biggest_height=0;//最大分辨率
int fitSize_width=0,fitSize_height=0;
int fitSize_widthBig=0,fitSize_heightBig=0;
Camera.Parameters parameters = mCamera.getParameters();
//获得系统支持视频格式
List<Camera.Size> videoSize = parameters.getSupportedVideoSizes();
for(int i = 0;i < videoSize.size();i++){
int w = videoSize.get(i).width;
int h = videoSize.get(i).height;
if ((biggest_width == 0 && biggest_height == 0)||
(w >= biggest_height && h >= biggest_width)) {
biggest_width = w;
biggest_height = h;
}
if(w == screenHeight && h == screenWidth){
width = w;
height = h;
}else if(w == screenHeight || h == screenWidth){
if(width == 0 || height == 0){
fitSize_width = w;
fitSize_height = h;
}else if(w < screenHeight || h < screenWidth){
fitSize_widthBig = w;
fitSize_heightBig = h;
}
}
}
if(width == 0 && height == 0){
width = fitSize_width;
height = fitSize_height;
}
if(width == 0 && height == 0){
width = fitSize_widthBig;
height = fitSize_heightBig;
}
if(width == 0 && height == 0){
width = biggest_width;
height = biggest_height;
}
}
复制代码
在初始化相机方法调用,而且建立MediaRecorder
对象:
@Override
public void surfaceCreated(SurfaceHolder holder) {
//surface建立时执行
if (mCamera == null) {
openCamera(mCameraId);
}
//并设置预览
startPreview();
//新增获取系统支持视频
getVideoSize();
mediaRecorder = new MediaRecorder();
}
复制代码
//解锁Camera硬件
mCamera.unlock();
mediaRecorder.setCamera(mCamera);
//音频源 麦克风
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
//视频源 camera
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
//输出格式
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//音频编码
mediaRecorder.setAudioEncoder(MediaRecorder.VideoEncoder.DEFAULT);
//视频编码
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//设置帧频率
mediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);
Log.d("sssd视频宽高:","宽"+width+"高"+height+"");
mediaRecorder.setVideoSize(width,height);
//每秒的帧数
mediaRecorder.setVideoFrameRate(24);
复制代码
若是不设置调整保存视频的角度,用后置录制视频会逆时针翻转90度,因此须要设置输出顺时针旋转90度:
//调整视频旋转角度 若是不设置 后置和前置都会被旋转播放
if(mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
if(orientation == 270 || orientation == 90 || orientation == 180){
mediaRecorder.setOrientationHint(180);
}else{
mediaRecorder.setOrientationHint(0);
}
}else{
if(orientation == 90){
mediaRecorder.setOrientationHint(90);
}
}
复制代码
整个录制方法以下:
/** * * 录制方法 */
public void startRecord(String path,String name){
//解锁Camera硬件
mCamera.unlock();
mediaRecorder.setCamera(mCamera);
//音频源 麦克风
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
//视频源 camera
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
//输出格式
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//音频编码
mediaRecorder.setAudioEncoder(MediaRecorder.VideoEncoder.DEFAULT);
//视频编码
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//设置帧频率
mediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);
Log.d("sssd视频宽高:","宽"+width+"高"+height+"");
mediaRecorder.setVideoSize(width,height);
//每秒的帧数
mediaRecorder.setVideoFrameRate(24);
//调整视频旋转角度 若是不设置 后置和前置都会被旋转播放
if(mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
if(orientation == 270 || orientation == 90 || orientation == 180){
mediaRecorder.setOrientationHint(180);
}else{
mediaRecorder.setOrientationHint(0);
}
}else{
if(orientation == 90){
mediaRecorder.setOrientationHint(90);
}
}
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
//设置输出文件名字
mediaRecorder.setOutputFile(path + File.separator + name + "mp4");
File file1 = new File(path + File.separator + name + "mp4");
if(file1.exists()){
file1.delete();
}
//设置预览
mediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
try {
//准备录制
mediaRecorder.prepare();
//开始录制
mediaRecorder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
当中止录制后须要把MediaRecorder
释放,而且从新调用预览方法:
/** * * 中止录制 */
public void stopRecord(){
if(mediaRecorder != null){
mediaRecorder.release();
mediaRecorder = null;
}
if(mCamera != null){
mCamera.release();
}
openCamera(mCameraId);
//并设置预览
startPreview();
}
复制代码
mCameraPresenter.startRecord(Configuration.OUTPATH,"video");
复制代码
当录制完须要播放,用新的界面来,用SurfaceView
+MediaPlayer
来实现:
public class PlayAudioActivity extends AppCompatActivity implements MediaPlayer.OnCompletionListener,MediaPlayer.OnPreparedListener{
private SurfaceView sf_play;
private MediaPlayer player;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playaudio);
sf_play = findViewById(R.id.sf_play);
//下面开始实例化MediaPlayer对象
player = new MediaPlayer();
player.setOnCompletionListener(this);
player.setOnPreparedListener(this);
//设置数据数据源,也就播放文件地址,能够是网络地址
String dataPath = Configuration.OUTPATH + "/videomp4";
try {
player.setDataSource(dataPath);
} catch (Exception e) {
e.printStackTrace();
}
sf_play.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
//将播放器和SurfaceView关联起来
player.setDisplay(holder);
//异步缓冲当前视频文件,也有一个同步接口
player.prepareAsync();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
}
/** * * 设置循环播放 * @param mp */
@Override
public void onCompletion(MediaPlayer mp) {
player.start();
player.setLooping(true);
}
/** * 这边播放 * @param mp */
@Override
public void onPrepared(MediaPlayer mp) {
player.start();
}
/** * 释放资源 * */
@Override
protected void onDestroy(){
super.onDestroy();
if(player != null){
player.reset();
player.release();
player = null;
}
}
}
复制代码
实际效果:
下面实现人脸检测,注意是人脸检测不是人脸识别,步骤以下:
在相机调用开启预览后才能调用:
/** * 开始预览 */
private void startPreview() {
try {
//根据所传入的SurfaceHolder对象来设置实时预览
mCamera.setPreviewDisplay(mSurfaceHolder);
//调整预览角度
setCameraDisplayOrientation(mAppCompatActivity,mCameraId,mCamera);
mCamera.startPreview();
//开启人脸检测
startFaceDetect();
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
/** * 人脸检测 */
private void startFaceDetect() {
//开始人脸检测,这个要调用startPreview以后调用
mCamera.startFaceDetection();
//添加回调
mCamera.setFaceDetectionListener(new Camera.FaceDetectionListener() {
@Override
public void onFaceDetection(Camera.Face[] faces, Camera camera) {
// mCameraCallBack.onFaceDetect(transForm(faces), camera);
mFaceView.setFace(transForm(faces));
Log.d("sssd", "检测到" + faces.length + "人脸");
for(int i = 0;i < faces.length;i++){
Log.d("第"+(i+1)+"张人脸","分数"+faces[i].score+"左眼"+faces[i].leftEye+"右眼"+faces[i].rightEye+"嘴巴"+faces[i].mouth);
}
}
});
}
复制代码
在Face
源码中,能够看到这么一段描述:
Bounds of the face. (-1000, -1000) represents the top-left of the
camera field of view, and (1000, 1000) represents the bottom-right of
the field of view. For example, suppose the size of the viewfinder UI
is 800x480. The rect passed from the driver is (-1000, -1000, 0, 0).
The corresponding viewfinder rect should be (0, 0, 400, 240). It is
guaranteed left < right and top < bottom. The coordinates can be
smaller than -1000 or bigger than 1000. But at least one vertex will
be within (-1000, -1000) and (1000, 1000).
<p>The direction is relative to the sensor orientation, that is, what
the sensor sees. The direction is not affected by the rotation or
mirroring of {@link #setDisplayOrientation(int)}. The face bounding
rectangle does not provide any information about face orientation.</p>
<p>Here is the matrix to convert driver coordinates to View coordinates
in pixels.</p>
<pre>
Matrix matrix = new Matrix();
CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
// Need mirror for front camera.
boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
matrix.setScale(mirror ? -1 : 1, 1);
// This is the value for android.hardware.Camera.setDisplayOrientation.
matrix.postRotate(displayOrientation);
// Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
// UI coordinates range from (0, 0) to (width, height).
matrix.postScale(view.getWidth() / 2000f, view.getHeight() / 2000f);
matrix.postTranslate(view.getWidth() / 2f, view.getHeight() / 2f);
</pre>
@see #startFaceDetection()
复制代码
具体意思是在人脸使用的坐标和安卓屏幕坐标是不同的,而且举了一个例子:若是屏幕尺寸是800*480,如今有一个矩形位置在人脸坐标系中位置是(-1000,-1000,0,0),那么在安卓屏幕坐标的位置是(0,0,400,240)。
而且给了转换坐标的具体方法:
/** * 将相机中用于表示人脸矩形的坐标转换成UI页面的坐标 * * @param faces 人脸数组 * @return */
private ArrayList<RectF> transForm(Camera.Face[] faces) {
Matrix matrix = new Matrix();
boolean mirror;
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
mirror = true;
} else {
mirror = false;
}
//前置须要镜像
if (mirror) {
matrix.setScale(-1f, 1f);
} else {
matrix.setScale(1f, 1f);
}
//后乘旋转角度
matrix.postRotate(Float.valueOf(orientation));
//后乘缩放
matrix.postScale(mSurfaceView.getWidth() / 2000f,mSurfaceView.getHeight() / 2000f);
//再进行位移
matrix.postTranslate(mSurfaceView.getWidth() / 2f, mSurfaceView.getHeight() / 2f);
ArrayList<RectF> arrayList = new ArrayList<>();
for (Camera.Face rectF : faces) {
RectF srcRect = new RectF(rectF.rect);
RectF dstRect = new RectF(0f, 0f, 0f, 0f);
//经过Matrix映射 将srcRect放入dstRect中
matrix.mapRect(dstRect, srcRect);
arrayList.add(dstRect);
}
return arrayList;
}
复制代码
package com.knight.cameraone.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import java.util.ArrayList;
/** * @author created by knight * @organize * @Date 2019/10/11 13:54 * @descript:人脸框 */
public class FaceDeteView extends View {
private Paint mPaint;
private String mColor = "#42ed45";
private ArrayList<RectF> mFaces = null;
public FaceDeteView(Context context) {
super(context);
init(context);
}
public FaceDeteView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public FaceDeteView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context){
mPaint = new Paint();
//画笔颜色
mPaint.setColor(Color.parseColor(mColor));
//只绘制图形轮廓
mPaint.setStyle(Paint.Style.STROKE);
//设置粗细
mPaint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1f,context.getResources().getDisplayMetrics()));
//设置抗锯齿
mPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
if(mFaces != null){
for(RectF face:mFaces){
canvas.drawRect(face,mPaint);
}
}
}
/** * 设置人人脸信息 */
public void setFace(ArrayList<RectF> mFaces){
this.mFaces = mFaces;
//重绘矩形框
invalidate();
}
}
复制代码
布局文件:
<SurfaceView android:id="@+id/sf_camera" android:layout_width="match_parent" android:layout_height="match_parent"/>
<!-- 新增 -->
<com.knight.cameraone.view.FaceDeteView android:id="@+id/faceView" android:layout_width="match_parent" android:layout_height="match_parent"/>
复制代码
并增长人脸检测开关:
/** * 开启人脸检测 * */
public void turnFaceDetect(boolean isDetect){
mFaceView.setVisibility(isDetect ? View.VISIBLE : View.GONE);
}
复制代码
这里只是将自定义View不显示,具体效果图以下:
vivo
安卓7.1.1版本下,眼睛,嘴巴数据是获取不到的。
到这里自定义相机Camera1步骤再次梳理以下:
SurfaceView
/TextureView
),进行实时显示相机预览图像SurfaceView
获取的SurfaceHolde
r设置SurfaceHolder.Callback
监听,实现surfaceCreated
、surfaceChanged
和surfaceDestroyed
方法/若是是TextureView
的话就设置setSurfaceTextureListener
监听并实现onSurfaceTextureAvailable
、onSurfaceTextureSizeChanged
、onSurfaceTextureDestroyed
和onSurfaceTextureUpdated
方法SurfaceView
->surfaceCreated
/TextureView
->onSurfaceTextureAvailable
方法经过Camera.open(int cameraId)
打开相机Camera.getParameters()
获取Parameters
对象而且设置具体参数Parameters
对象经过Camera.setParameters(Parameters parames)
设置进Camera中Camera.startPreview()
开启预览takePicture(ShutterCallback shutter, PictureCallback raw,PictureCallback jpeg)
,在回调的onPictureTaken(byte[] data, Camera camera)
返回的字节数组里保存图片,保存图片按需是否须要旋转设置SurfaceView
->surfaceDestroyed
/TextureView
->onSurfaceTextureUpdated
进行资源释放