Android Studio 调用Camera实现拍照功能

今天写个Camera拍照的教程吧。本例子的流程为   首先通过SurfaceView将Camera的实时画面显示在屏幕上,然后通过点击拍照对当前画面进行捕捉,*后将获得的图片保存至本地。

首先创建一个SurfaceHolder实现对SurfaceView的回调,然后重写SurfaceCreate函数,实现对Camera的初始化等一系列工作:代码如下:
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.e(“TAG”,”——surfaceCreated——“);
try {
//这里我优先找后置摄像头,找不到再找前面的
int cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
if (cameraIndex == -1) {
cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
if (cameraIndex == -1) {
Log.e(“TAG”, “No Camera!”);
currentCameraType = CAMERA_NOTEXIST;
currentCameraIndex = -1;
return;
} else {
currentCameraType = FRONT;
}
} else {
currentCameraType = BACK;
}

//找到想要的摄像头后,就打开
if (mCamera == null) {
mCamera = openCamera(currentCameraType);
}

} catch (Exception e) {
e.printStackTrace();
}
}
本例子中,我首先找到想要打开的摄像头,这里的优先寻找后置摄像头,如果没有找到,再找前置的,代码如下:

/**
* 按要求查找摄像头
*
* @param camera_facing 按要求查找,镜头是前还是后
* @return -1表示找不到
*/
private int findBackOrFrontCamera(int camera_facing) {
int cameraCount = 0;
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
cameraCount = Camera.getNumberOfCameras();
for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraInfo.facing == camera_facing) {
return camIdx;
}
}
return -1;
}
当找到摄像头后,便打开Camera,其实打开Camera可以直接用open(CameraId)函数即可,但我在重新封装了一下,直接帖代码:

/**
* 按照type的类型打开相应的摄像头
*
* @param type 标志当前打开前还是后的摄像头
* @return 返回当前打开摄像机的对象
*/
private Camera openCamera(int type) {
int frontIndex = -1;
int backIndex = -1;
int cameraCount = Camera.getNumberOfCameras();

Camera.CameraInfo info = new Camera.CameraInfo();
for (int cameraIndex = 0; cameraIndex < cameraCount; cameraIndex++) {
Camera.getCameraInfo(cameraIndex, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
frontIndex = cameraIndex;
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
backIndex = cameraIndex;
}
}

currentCameraType = type;
if (type == FRONT && frontIndex != -1) {
currentCameraIndex = frontIndex;
return Camera.open(frontIndex);
} else if (type == BACK && backIndex != -1) {
currentCameraIndex = backIndex;
return Camera.open(backIndex);
}
return null;
}
然后在SurfaceChange对Camera进行一系列初始化(对摄像头初始化,就是设定图片格式,图片尺寸等赋值,要打开摄像头才可以初始化,否则会报错)
/**
* 初始化摄像头
* @param holder
*/
private void initCamera(SurfaceHolder holder){
Log.e(“TAG”,”initCamera”);
if (mPreviewRunning)
mCamera.stopPreview();

Camera.Parameters parameters;
try{
//获取预览的各种分辨率
parameters = mCamera.getParameters();
}catch (Exception e){
e.printStackTrace();
return;
}
//这里我设为480*800的尺寸
parameters.setPreviewSize(480,800);
// 设置照片格式
parameters.setPictureFormat(PixelFormat.JPEG);
//设置图片预览的格式
parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);
setCameraDisplayOrientation(this,currentCameraIndex,mCamera);
try{
mCamera.setPreviewDisplay(holder);
}catch(Exception e){
if(mCamera != null){
mCamera.release();
mCamera = null;
}
e.printStackTrace();
}
mCamera.startPreview();
mPreviewRunning = true;
}

/**
* 设置旋转角度
* @param activity
* @param cameraId
* @param camera
*/
private void setCameraDisplayOrientation(Activity activity,int cameraId,Camera camera){
Camera.CameraInfo info = new Camera.CameraInfo();
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;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
result = (info.orientation + degrees) % 360;
result = (360 – result) % 360;
}else{
result = (info.orientation – degrees +360) % 360;
}
camera.setDisplayOrientation(result);
}

当这些工作完成后,便可以对当前摄像头捕捉的画面进行拍照了:
/**
* 实现拍照功能
*/
public void takePhoto(){
Camera.Parameters parameters;
try{
parameters = mCamera.getParameters();
}catch(Exception e){
e.printStackTrace();
return;
}
//获取摄像头支持的各种分辨率,因为摄像头数组不确定是按降序还是升序,这里的逻辑有时不是很好找得到相应的尺寸
//可先确定是按升还是降序排列,再进对对比吧,我这里拢统地找了个,是个不精确的…
List<Camera.Size> list = parameters.getSupportedPictureSizes();
int size = 0;
for (int i =0 ;i < list.size() – 1;i++){
if (list.get(i).width >= 480){
//完美匹配
size = i;
break;
}
else{
//找不到就找个*接近的吧
size = i;
}
}
//设置照片分辨率,注意要在摄像头支持的范围内选择
parameters.setPictureSize(list.get(size).width,list.get(size).height);
//设置照相机参数
mCamera.setParameters(parameters);

//使用takePicture()方法完成拍照
mCamera.autoFocus(new Camera.AutoFocusCallback() {
//自动聚焦完成后拍照
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success && camera != null){
mCamera.takePicture(new ShutterCallback(), null, new Camera.PictureCallback() {
//拍照回调接口
@Override
public void onPictureTaken(byte[] data, Camera camera) {
savePhoto(data);
//停止预览
mCamera.stopPreview();
//重启预览
mCamera.startPreview();
}
});
}
}
});
}

/* *//**
* 快门回调接口,如果不想拍照声音,直接将new ShutterCallback()修改为null即可
*/
private class ShutterCallback implements Camera.ShutterCallback {
@Override
public void onShutter() {
MediaPlayer mPlayer = new MediaPlayer();
mPlayer = MediaPlayer.create(getApplicationContext(), R.raw.shutter);
try{
mPlayer.prepare();
}catch (IllegalStateException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
mPlayer.start();
}
}
这里要注意下,有些手机调用onAutoFocus函数,会返回失败,因为如果该手机无自动对焦,则无法执行对焦成功后的函数了。。。

当捕捉到数据后,便可以将这些数据保存至设定的地方了:

/**
* 设置照片的路径,具体路径可自定义
* @return
*/
private String setPicSaveFile(){
//创建保存的路径
File storageDir = getOwnCacheDirectory(this,”MyCamera/photos”);
//返回自定义的路径
return storageDir.getPath();
}

private File getOwnCacheDirectory(Context context, String cacheDir) {
File appCacheDir = null;
//判断SD卡正常挂载并且拥有根限的时候创建文件
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
hasExternalStoragePermission(context)){
appCacheDir = new File(Environment.getExternalStorageDirectory(),cacheDir);
}
if (appCacheDir == null || !appCacheDir.exists() && !appCacheDir.mkdirs()){
appCacheDir = context.getCacheDir();
}
return appCacheDir;
}

/**
* 检查是否有权限
* @param context
* @return
*/
private boolean hasExternalStoragePermission(Context context) {
int permission = context.checkCallingOrSelfPermission(“android.permission.WRITE_EXTERNAL_STORAGE”);
//PERMISSION_GRANTED=0
return permission == 0;
}
由于调用了系统Camera和对SD卡读写,所以在AndroidManifest需要申请权限:

<!–摄像头相关权限–>
<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />
<uses-feature android:name=”android.hardware.camera.autofocus” />
<!– 在SDCard中创建与删除文件权限 –>
<uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>
<!– 往SDCard写入数据权限 –>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
 

后记:google在Android5.0后推出了Camera的升级版—Camera2:

按照Android的官方说明,camera 2支持以下5点新特性,有兴趣的可以研究下:

(1)支持每秒30帧的全高清连拍。
(2)支持在每帧之间使用不同的设置。
(3)支持原生格式的图像输出。
(4)支持零延迟快门和电影速拍。
(5)支持相机在其他方面的手动控制,比如设置噪音消除的级别。