简单开发相机


最近开发自定义相机,简单的实现了拍照功能,也适配了相机的预览变形问题,拍照的旋转角度问题。
开发时使用的是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();
        }

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值