[Android(安卓)相机]Android(安卓)相机开发的基本流程
版权声明:本文使用https://creativecommons.org/licenses/by-nc-nd/4.0/规定的《署名-非商业性使用-禁止演绎 4.0 国际》协议 https://blog.csdn.net/bluewindtalker/article/details/54563910
相机开发现在有2个类,分别为android.hardware.camera2和android.hardware.Camera,其中Camera类官方已经不推荐,不过鉴于有前人踩坑了,为了快速开发也就直接拿来用了
This class was deprecated in API level 21.
We recommend using the new android.hardware.camera2
API for new applications.
关于旧版的Camera类,google官方给了下面的指导步骤,https://developer.android.com/reference/android/hardware/Camera.html
To take pictures with this class, use the following steps:
看了这些后我们可以简单的进行实战,首先是初始化camera的过程。
/** * 初始化照片 */ private void initCamera() { if (camera != null) { camera.startPreview(); } Log.e(TAG, "initCamera"); //1. Obtain an instance of Camera from open(int). //这里可以根据前后摄像头设置 camera = openCamera(currentCameraType); if (camera == null) { return; } //2. Get existing (default) settings with getParameters(). //获得存在的默认配置属性 Camera.Parameters parameters = camera.getParameters(); //3. If necessary, modify the returned Camera.Parameters object and call setParameters(Camera.Parameters). //可以根据需要修改属性,这些属性包括是否自动持续对焦、拍摄的gps信息、图片视频格式及大小、预览的fps、 // 白平衡和自动曝光补偿、自动对焦区域、闪光灯状态等。 //具体可以参阅https://developer.android.com/reference/android/hardware/Camera.Parameters.html if (parameters.getSupportedFocusModes().contains(Camera.Parameters .FOCUS_MODE_CONTINUOUS_PICTURE)) { //自动持续对焦 parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); } //在设置图片和预览的大小时要注意当前摄像头支持的大小,不同手机支持的大小不同,如果你的SurfaceView不是全屏,有可能被拉伸。 // parameters.getSupportedPreviewSizes(),parameters.getSupportedPictureSizes() List picSizes = parameters.getSupportedPictureSizes(); Resources resources = this.getResources(); DisplayMetrics dm = resources.getDisplayMetrics(); float density = dm.density; int width = dm.widthPixels; int height = dm.heightPixels; Camera.Size picSize = getPictureSize(picSizes, width, height); parameters.setPictureSize(picSize.width, picSize.height); camera.setParameters(parameters); //4. Call setDisplayOrientation(int) to ensure correct orientation of preview. //你可能会遇到画面方向和手机的方向不一致的问题,竖向手机的时候,但是画面是横的,这是由于摄像头默认捕获的画面横向的 // 通过调用setDisplayOrientation来设置PreviewDisplay的方向,可以解决这个问题。 setCameraDisplayOrientation(this, currentCameraType, camera); //5. Important: Pass a fully initialized SurfaceHolder to setPreviewDisplay(SurfaceHolder). // Without a surface, the camera will be unable to start the preview. //camera必须绑定一个surfaceview才可以正常显示。 try { camera.setPreviewDisplay(displaySfv.getHolder()); } catch (IOException e) { e.printStackTrace(); } //6. Important: Call startPreview() to start updating the preview surface. // Preview must be started before you can take a picture. //在调用拍照之前必须调用startPreview()方法,但是在此时有可能surface还未创建成功。 // 所以加上SurfaceHolder.Callback(),在回调再次初始化下。 camera.startPreview(); //7. When you want, call // takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback) // to capture a photo. Wait for the callbacks to provide the actual image data. //当如果想要拍照的时候,调用takePicture方法,这个下面我们会讲到。 //8. After taking a picture, preview display will have stopped. To take more photos, call startPreview() again first. //在拍照结束后相机预览将会关闭,如果要再次拍照需要再次调用startPreview() //9. Call stopPreview() to stop updating the preview surface. //通过调用stopPreview方法可以结束预览 //10. Important: Call release() to release the camera for use by other applications. // Applications should release the camera immediately in onPause()(and re-open() it in onResume()). //建议在onResume调用open的方法,在onPause的时候执行release方法 }
根据上文提到的第9、10步骤我们在onResume与onPause做处理。 @Override protected void onResume() { super.onResume(); Log.e(TAG, "onResume"); if (!isRequestPermission) { checkAndInitCamera(); } } @Override protected void onPause() { super.onPause(); Log.e(TAG, "onPause"); releaseCamera(); } private void releaseCamera() { if (camera != null) { camera.stopPreview(); camera.release(); camera = null; } }
其中checkAndInitCamera()为权限处理的方法
private void checkAndInitCamera() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 检查该权限是否已经获取 int i = ContextCompat.checkSelfPermission(this, permissions[0]); // 权限是否已经 授权 GRANTED---授权 DINIED---拒绝 if (i != PackageManager.PERMISSION_GRANTED) { // 如果没有授予该权限,就去提示用户请求 isRequestPermission = true; ActivityCompat.requestPermissions(this, permissions, CAMERA_PERMISSION_CODE); } else { initCamera(); } } else { initCamera(); } }
这里有些细节需要注意,在 parameters.setPictureSize(int width , int height ); 这个方法的时候 不能将宽高随意写,必须从 parameters.getSupportedPictureSizes(); 中选择最合适的宽高,否则会出现setParameters failed的运行时错误。
而系统提供的宽高是根据摄像头的参数定的,这个导致需要根据手机和surfaceview宽高来动态适配,否则可能会出现图像失真拉伸压缩的情况,在本文中将直接使用最接近的摄像头像素的算法
/** * 获得最合是的宽高size */ private Camera.Size getPictureSize(List picSizes, int width, int height) { Camera.Size betterSize = null; int diff = Integer.MAX_VALUE; if (picSizes != null && picSizes.size() > 0) { for (Camera.Size size : picSizes) { int newDiff = Math.abs(size.width - width) + Math.abs(size.height - height); if(newDiff == 0){ return size; } if (newDiff < diff) { betterSize = size; diff = newDiff; } } } return betterSize; }
还有一个细节是摄像头并不是正的,调用的方法 setCameraDisplayOrientation
//设置相机的方向 public int 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); 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; default: degrees = 0; 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); return degrees; }
https://blog.csdn.net/bluewindtalker/article/details/54563910 这么做运行的时候,我们发现预览图并不能正常显示出来,这是因为surface还没有正常创建出来,这时候我们可以在initCamera方法中加入如下代码,坚挺SurfaceHolder的事件回调
SurfaceHolder holder = displaySfv.getHolder(); if (holder != null) { holder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { Log.e(TAG, "surfaceCreated" + holder); checkAndInitCamera(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.e(TAG, "surfaceChanged" + holder); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.e(TAG, "surfaceDestroyed" + holder); } }); }
以下就是重点了,拍照,拍照就是触发一个回调事件方法。
/** * 拍摄照片 */ private void takePicture() { picIV.setImageBitmap(null); if (camera == null) { return; } //如果不加第一个回调,手机会没有拍照音效,第二个回调是返回raw格式图片, // 了解过相机的人可能知道这是原图的意思,这个我们不处理,我们处理第三个回调,jpg格式的数据 // 拍摄照片 camera.takePicture(new Camera.ShutterCallback() { @Override public void onShutter() { } }, null, new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // 将拍照数据data数组转化为Bitmap,这里应该放到线程执行了,这里为了简单处理直接放UI线程了 Bitmap imageBitmap = BitmapFactory.decodeByteArray(data, 0, data.length); //一般手机需要旋转90度来适应方向,如果setCameraDisplayOrientation得到的结果不是90度,一般还需要再次旋转180 picIV.setImageBitmap(rotate(imageBitmap, 90)); picFl.setVisibility(View.VISIBLE); } }); } public Bitmap rotate(Bitmap bitmap, int degree) { Matrix matrix = new Matrix(); matrix.postRotate(degree); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); }
以下是布局文件内容
<?xml version="1.0" encoding="utf-8"?>
下篇将讲解如何通过摄像头识别周围环境光亮强度
[Android相机]光线传感器识别环境光亮强度
[Android相机]通过手机摄像头识别环境亮度
git地址
https://github.com/bluewindtalker/camerademo
更多相关文章
- Android(安卓)知识点积累(一)
- android判断是否联网
- Java/Android(安卓)Annotation processor实践:greendaoannotation
- android 在listview上的 gallery 禁止上下滑动
- Android中更详细的log获取方法
- RatingBar的使用方法
- Android事件分发机制解析
- Android: Gallery的adapter中getView方法被执行多次
- Android(安卓)ViewDragHelper使用介绍