简单开发相机
2016-04-24 17:50
253 查看
最近开发自定义相机,简单的实现了拍照功能,也适配了相机的预览变形问题,拍照的旋转角度问题。
开发时使用的是Camera类,介绍Camera简单的操作方法:
首先加入权限:
之后就可以开始了,相机预览需要surfaceView,所以先建立好surfaceView并现实SurfaceHolder.CallBack接口。
通过SurfaceView.getHolder().addCallback()添加实现了SurfaceHolder.CallBack接口的类。之后就可以编写相机操作了,编写相机的操作可以参照官方的文档,上面也简单地介绍了,不难。这里主要介绍下开发遇到的问题和解决方法:
1).相机预览时图像旋转,以及变形问题。
2).相机各个角度拍摄(前后摄像头的各个方向的拍摄)相片适应问题。
问题1:预览旋转是因为相机的方向与手机竖屏时角度是成90度,手机横屏时与相机的方向一致;预览变形是因为预览的SurfaceView的尺寸与相机预览的相片数据尺寸相差比较大照成的。解决方法可以判断手机当前与相机的角度,在进行设置预览的角度就行了。预览变形需要获取去SurfaceView相匹配的比例进行预览。不过预览的设置要知道相机的原始比例是宽比高(举个例子当你手机竖屏时预览90度后适配你的屏幕需要屏幕的高比宽),看下面代码:
设置之后手机的预览就正常了,接下来就是设置预览的尺寸:
获取匹配的预览尺寸,获取规则:先将尺寸分辨率从小到大排序,变量可支持的预览尺寸与目标尺寸比例若相差0.01则返回这个尺寸。若没有则在次遍历,得到与目标尺寸比例相差小的,为了预览清晰满足相差0.1也可以。
预览部分就设置完成,接下来是拍照问题,拍照问题需要解决的拍照后相片的旋转问题,由于要匹配大部分手机例如三星的手机,三星手机将相片旋转问题(由于要获取相片的bitmap再进行旋转)。我这里限制了相片的清晰度最高为1920。
这方法的意思:info.orientation 该是摄像头与自然方向的角度差(项目中是竖屏方向),即摄像头需要顺时针转过多少度才能恢复到自然方向。当摄像头为后置摄像头时,顺时针为正,逆时针为负(即横拍时需要加上该值a度,即(info.orientation + a)这个值, 调用parameters.setRotation((info.orientation + a))方法就可以了(好像只能支持0,90,180,270这些特殊的角度)。当摄像头为前置摄像头,顺时针为负,逆时针为正。
设置后拍照角度就可以设置拍照尺寸,这个比较随意,一般推荐与屏幕相匹配的尺寸或者跟预览匹配的尺寸
设置完这些就可以拍照 了。通过实现PictureCallback接口返回数据,我这里只返回jpeg,通过raw处理过的数据。
}
开发时使用的是Camera类,介绍Camera简单的操作方法:
Camera.open() //是获取相机实例。可加参数CameraId获取不同方向的相机实例。 Cmaera.setPreviewDisplay() //将相机与surface连接起来,将相机的数据绘制到界面上。 Camera.startPreview() //开始预览不过预览之前需要绑定surface将相机与界面连接起来。 Camera.stopPreview() //停止预览。 Camera.takePhoto() //拍照。 Camera.autoFocus() //自动对焦。 Camera.release() //释放相机实例。
首先加入权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
之后就可以开始了,相机预览需要surfaceView,所以先建立好surfaceView并现实SurfaceHolder.CallBack接口。
@Override public void surfaceCreated(SurfaceHolder holder) { // SurfaceView创建时 // 一般Camera.setPreviewDisplay(holder)在这边操作 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // SurfaceView的大小改变 // 一般Camera.startPreview()在这边操作 } @Override public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView的销毁 }
通过SurfaceView.getHolder().addCallback()添加实现了SurfaceHolder.CallBack接口的类。之后就可以编写相机操作了,编写相机的操作可以参照官方的文档,上面也简单地介绍了,不难。这里主要介绍下开发遇到的问题和解决方法:
1).相机预览时图像旋转,以及变形问题。
2).相机各个角度拍摄(前后摄像头的各个方向的拍摄)相片适应问题。
问题1:预览旋转是因为相机的方向与手机竖屏时角度是成90度,手机横屏时与相机的方向一致;预览变形是因为预览的SurfaceView的尺寸与相机预览的相片数据尺寸相差比较大照成的。解决方法可以判断手机当前与相机的角度,在进行设置预览的角度就行了。预览变形需要获取去SurfaceView相匹配的比例进行预览。不过预览的设置要知道相机的原始比例是宽比高(举个例子当你手机竖屏时预览90度后适配你的屏幕需要屏幕的高比宽),看下面代码:
public void startPreview(Activity activity, SurfaceView surfaceView) { if (null == mCamera) return; setCameraDisplayOrientation(activity); //在预览之前先设置预览的角度 setupPreviewSize(surfaceView); //在预览之间设置预览的图片尺寸 mCamera.startPreview(); //开始预览 } public void setCameraDisplayOrientation(Activity activity) { if (null == mCamera) return; CameraInfo info = new CameraInfo(); Camera.getCameraInfo(mCameraId, info); int degrees = getDisplayRotation(activity); int result; if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; } else { // back-facing result = (info.orientation - degrees + 360) % 360; } mCamera.setDisplayOrientation(result); } public int getDisplayRotation(Activity activity) { int rotation = activity.getWindowManager().getDefaultDisplay() .getRotation(); switch (rotation) { case Surface.ROTATION_0: return 0; case Surface.ROTATION_90: return 90; case Surface.ROTATION_180: return 180; case Surface.ROTATION_270: return 270; } return 0; }
设置之后手机的预览就正常了,接下来就是设置预览的尺寸:
private void setupPreviewSize(SurfaceView surfaceView) { Parameters parameters = mCamera.getParameters(); List<Size> sizes = parameters.getSupportedPreviewSizes();//获取相机支持的预览尺寸 int width = surfaceView.getWidth(); int height = surfaceView.getHeight(); Size size = CameraSizeUntil.getOptimalPreviewSize(sizes, height, width);//得到匹配的尺寸 parameters.setPreviewSize(size.width, size.height); setParameters(parameters); }
获取匹配的预览尺寸,获取规则:先将尺寸分辨率从小到大排序,变量可支持的预览尺寸与目标尺寸比例若相差0.01则返回这个尺寸。若没有则在次遍历,得到与目标尺寸比例相差小的,为了预览清晰满足相差0.1也可以。
public static Size getOptimalPreviewSize(List<Size> sizes, double height,double width) { final double ASPECT_TOLERANCE = 0.01; double targetRatio = height/width; //****注意与size.width/size.height比较 if (sizes == null) return null; Size optimalSize = null; double minDiff = Double.MAX_VALUE; Collections.sort(sizes, new MyComparator());//从小到大排序 for (Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.width - targetHeight) < minDiff) { optimalSize = size; } } if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) < minDiff || Math.abs(ratio - targetRatio) < 0.1) { optimalSize = size; minDiff = Math.abs(ratio - targetRatio); } } } return optimalSize; }
预览部分就设置完成,接下来是拍照问题,拍照问题需要解决的拍照后相片的旋转问题,由于要匹配大部分手机例如三星的手机,三星手机将相片旋转问题(由于要获取相片的bitmap再进行旋转)。我这里限制了相片的清晰度最高为1920。
public void takePhoto(Context context, ShutterCallback shutter, PictureCallback raw, PictureCallback jpeg) { if (null == mCamera) return; setTakePictureOientation(context); //设置拍照角度,虽然我们设置了预览的角度,但拍照的相片数据时没有经过角度处理的数据,是由相机直接获取,没有经过别的处理。 setUpTakePictrueSize(); //设置相片的尺寸 mCamera.takePicture(shutter, raw, jpeg); //拍照 } private void setTakePictureOientation(Context context) { Parameters parameters = mCamera.getParameters(); parameters.setRotation(getTakePicRotation(context)); //设置拍照的角度 setParameters(parameters); } private int getTakePicRotation (Context context) { if (null == mCameraOperate) return 0; Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(mCameraId, info); int degree = mSurfaceViewOrientation; //记录的与自然状态下的旋转角度 //if ((degree >= 0 && degree <=45) || degree >= 315 ) { // return info.orientation; //} int plusOrMinus = 1; if (1 == mStatus) //status 0是后置,1是前置,建议用CameraInfo直接来判断 plusOrMinus = -1; if (degree <315 && degree >= 225) { return Math.abs(info.orientation + plusOrMinus *270)%360; } else if (degree >= 45 && degree < 135) { return Math.abs(info.orientation + plusOrMinus * 90) % 360; } else if (degree >= 135 && degree <225) { return Math.abs(info.orientation + plusOrMinus * 180) % 360; } return Math.abs(info.orientation )% 360; }
这方法的意思:info.orientation 该是摄像头与自然方向的角度差(项目中是竖屏方向),即摄像头需要顺时针转过多少度才能恢复到自然方向。当摄像头为后置摄像头时,顺时针为正,逆时针为负(即横拍时需要加上该值a度,即(info.orientation + a)这个值, 调用parameters.setRotation((info.orientation + a))方法就可以了(好像只能支持0,90,180,270这些特殊的角度)。当摄像头为前置摄像头,顺时针为负,逆时针为正。
设置后拍照角度就可以设置拍照尺寸,这个比较随意,一般推荐与屏幕相匹配的尺寸或者跟预览匹配的尺寸
public static Size getOptimalTakePictureSize(List<Size> sizes) { if (null == sizes) return null; Collections.sort(sizes, new MyComparator()); Size size = null ; for (int i = sizes.size() - 1; i >= 0; i --) { size = sizes.get(i); if (size.width <= 1920) //限制宽度1920,由于要操作bitmap, 适配三星手机时避免OOM break; } if (null == size) { for (Size newSize :sizes) { size = newSize; } } return size; }
设置完这些就可以拍照 了。通过实现PictureCallback接口返回数据,我这里只返回jpeg,通过raw处理过的数据。
public static void saveImge (Context context,byte[] data,String fileName) { String path = saveTofile ( data,fileName); //将data通过输出流保存 int degree = getExifOrientation(path); //获取相片的旋转信息,主要是适配三星手机 if (0 != degree) { //若有旋转就将它旋转会正常,这一步需要转化为bitmap所以之前的操作限制了1920,避免oom(这个我没具体测试过到底多少为好) saveToNormal (data,path,degree); } MediaScannerConnection.scanFile(context, new String[]{path}, null, null); //通知系统更新,主要是系统的contentprovider不会马上更新,需要通知它马上更新。 } //保存图片 private static String saveTofile(byte[] data, String fileName) { // TODO Auto-generated method stub String path = null; final String ALBUM_PATH = Environment.getExternalStorageDirectory() + FILE_NAME; File dirFile = new File(ALBUM_PATH); if(!dirFile.exists()){ dirFile.mkdir(); } File myCaptureFile = new File(ALBUM_PATH + fileName); BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(myCaptureFile)); bos.write(data); path = myCaptureFile.getAbsolutePath(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (null != bos) { try { bos.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { bos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return path; } 旋转图片 private static void saveToNormal(byte[] data, String path,int degree) { // TODO Auto-generated method stub Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); Matrix matrix = new Matrix(); matrix.postRotate(degree); File file = new File(path); if (file.exists()) { file.delete(); } Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); bitmap.recycle(); OutputStream ops = null; try { ops = new BufferedOutputStream(new FileOutputStream(file)); newBitmap.compress(CompressFormat.JPEG, 100, ops); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (null != ops) { try { ops.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { ops.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } newBitmap.recycle(); }
}
相关文章推荐
- 稀疏编码(Sparse Coding)的前世今生(一) 转自http://blog.csdn.net/marvin521/article/details/8980853
- 20145311王亦徐 实验三 "敏捷开发与XP实践"
- LeetCode 344. Reverse String
- python 常用 语法 库函数
- ZOJ 3944-People Counting【模拟】(2016浙江省大学生程序设计竞赛)
- 1.1.程序运行为什么需要内存
- 人机交互—对win10自带输入法的评价
- CodeForces 273D|Dima and Figure|动态规划
- 工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)
- JAVA模式之责任链模式
- 用Disk Genius检测和修复硬盘坏道
- 机器学习中的相似性度量
- 【基础知识】——构造函数
- poj 1258 Agri-Net
- 20145229吴姗珊 《Java程序设计》第8周学习总结
- 解决 jQuery 符号 $ 与其他 javascript 库、框架冲突的问题
- spring 技术内幕--IOC
- 读写锁――――用互斥量和条件变量模拟
- beautifulsoup介绍和安装
- Android发展以及历史版本号