您的位置:首页 > 其它

简单开发相机

2016-04-24 17:50 253 查看
最近开发自定义相机,简单的实现了拍照功能,也适配了相机的预览变形问题,拍照的旋转角度问题。

开发时使用的是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();
}


}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: