Android自定义相机实践记录
前段时间的工作有这样一个需求,就是打开Android系统的摄像头,在预览模式Priview模式下获取原生的帧数据,这篇文章先不管这些。做完那个需求后,不过瘾,我就继续完善相机,实现了一个自定义的相机。写篇文章记录一下,总结一下相机应用的开发。
1、前置、后置摄像头的判断
一般手机都会有前置和后置摄像头,但是这里还是介绍一下判断的方法。
/**
* 判断是否有前置摄像头
*/
private boolean hasFrontCamera(){
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
//获取手机上摄像头的数量
int cameraCount = Camera.getNumberOfCameras();
for (int i = 0 ; i < cameraCount ; i++){
Camera.getCameraInfo(i,cameraInfo);
//循环判断
//同样,如果是判断后置摄像头,就需要Camera.CameraInfo.CAMERA_FACING_BACK
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
return true;
}
}
return true;
}
当前,判断前置摄像头还可以通过下面的方式判断
//是否有前置摄像头
isHasFrontCamera = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
//是否支持自动聚焦
isSupportAutoFocus = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS);
//是否支持闪光灯
isSupportFlash = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
//等等,还有其他的功能判断
2、SurfaceView的使用
开发相机,必然是对界面UI的刷新频率非常高,这就需要使用SurfaceView。关于这一段,网上到处都是,直接上代码:
public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback,View.OnClickListener,View.OnTouchListener{
private static final String TAG = "CameraActivity";
private SurfaceHolder mHolder;
private SurfaceView mSurfaceView;
private Button mBtnSearch;
private Button mBtnTakePhoto;
private View focusIndex;
private ImageView flashBtn;
private int mCurrentCameraId = 0; // 1是前置 0是后置
private Camera mCamera;
private CameraDoHelper mCameraHelper;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
initView();
initData();
}
private void initView() {
focusIndex = findViewById(R.id.focus_index);
flashBtn = (ImageView) findViewById(R.id.flash_view);
mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
//mCameraGrid = (CameraGrid) findViewById(R.id.camera_grid);
mBtnSearch = (Button) findViewById(R.id.search);
mBtnTakePhoto = (Button) findViewById(R.id.takephoto);
mSurfaceView.setOnTouchListener(this);
}
private void initData() {
//通过SurfaceView对象获取SurfaceHolder对象,我们主要使用它来处理相机应用
mHolder = mSurfaceView.getHolder();
//在这里需要注册回调接口,需要实现surfaceCreated,surfaceChanged,surfaceDestroyed三个方法。
mHolder.addCallback(this);
//保持屏幕常亮
mHolder.setKeepScreenOn(true);
}
@Override
protected void onResume() {
super.onResume();
// int numCams = Camera.getNumberOfCameras();
// if (numCams > 0) {
// try {
// mCurrentCameraId = 0;
// mCamera = Camera.open(mCurrentCameraId);
// mCamera.startPreview();
// } catch (RuntimeException ex) {
// Toast.makeText(getApplicationContext(), "未发现相机", Toast.LENGTH_LONG).show();
// }
// }
}
/*创建,或者重新创建
1,刚进入当前Activity时调用。
2、打开另一个Activity时会调用surfaceDestroyed
3、从另一个Activity再回来时,就会调用surfaceCreated
*/
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
Log.i(TAG,"surfaceCreated");
int numCams = Camera.getNumberOfCameras();
if (numCams > 0) {
try {
//后置摄像头为0,前置摄像头为1
mCurrentCameraId = 0;
//打开相机,并获取打开的相机的对象
mCamera = Camera.open(mCurrentCameraId);
} catch (RuntimeException ex) {
Toast.makeText(getApplicationContext(), "未发现相机", Toast.LENGTH_LONG).show();
}
}
if (mCamera != null){
//进行一系列的初始化,后面说
CameraDoHelper.getInstance(this).initCamera(mCamera,mCurrentCameraId);
}
}
//这个方法至少会在surfaceCreated之后调用。在尺寸发生变化时也会调用,比如横竖屏幕的转化
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
Log.i(TAG,"surfaceChanged");
if (surfaceHolder.getSurface() == null) {
return;
}
if (mCamera != null) {
try {
//这里就是让Camera对象和SurfaceView关联起来,让相机预览的界面放在SurfaceView上
mCamera.setPreviewDisplay(surfaceHolder);
//开始预览
mCamera.startPreview();
//自动对焦一次
CameraDoHelper.getInstance(this).reAutoFocus(mCamera);
} catch (IOException e) {
e.printStackTrace();
if (mCamera != null){
mCamera.release();
mCamera = null;
}
}
}
}
//另一个Activity覆盖当前的Activity时,会调用这个方法。再回来时,就会调用surfaceCreated方法
//点击退出当前Activity时,先于Ondestory
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
Log.i(TAG,"surfaceDestroyed");
if (mCamera != null){
//如果不加这句,退出Activity时,会出现异常
mCamera.setPreviewCallback(null);
//停止预览
mCamera.stopPreview();
//释放资源
mCamera.release();
mCamera = null;
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
/*case R.id.camera_back:
setResult(0);
finish();
break;*/
case R.id.camera_flip_view:
//转化前置或者后置摄像头
switchCamera();
break;
case R.id.flash_view:
turnLight();
break;
case R.id.action_button:
CameraDoHelper.getInstance(this).takePhoto(mCamera);
break;
case R.id.search: //处理选中状态
mBtnSearch.setSelected(true);
mBtnTakePhoto.setSelected(false);
break;
case R.id.takephoto: //处理选中状态
mBtnTakePhoto.setSelected(true);
mBtnSearch.setSelected(false);
break;
}
}
// 切换前后置摄像头
public void switchCamera() {
mCurrentCameraId = (mCurrentCameraId + 1) % Camera.getNumberOfCameras();
if (mCamera != null) {
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
//切换摄像头需要将前一个摄像头完全停止
try {
mCamera = Camera.open(mCurrentCameraId);
mCamera.setPreviewDisplay(mHolder);
CameraDoHelper.getInstance(this).initCamera(mCamera,mCurrentCameraId);
mCamera.startPreview();
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "未发现相机", Toast.LENGTH_LONG).show();
}
}
3、设置相机属性
我们可以设置相机的很多属性,比如相机预览界面的大小、照片格式与大小、闪光灯等。
public void initCamera(Camera camera,int cameraId){
if (camera != null) {
mCurrentCameraId = cameraId;
mCamera = camera;
//在这里获取当前相机的参数属性的对象
Camera.Parameters params = camera.getParameters();
//这里是一个获取最佳的预览size的方法
mPreviewSize = findBestPreviewResolution(camera);
//这里是一个获取最佳的照片size的方法
adapterSize = findBestPictureResolution(camera);
if (adapterSize != null) {
//设置拍照照片的size
params.setPictureSize(adapterSize.width, adapterSize.height);
}
if (mPreviewSize != null) {
//设置预览的size
params.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
}
//设置照片格式,这里是JPEG,还有
params.setPictureFormat(PixelFormat.JPEG);
//获取当前相机支持的聚焦模式,一般后置摄像头支持聚焦,前置摄像头不支持聚焦。
//聚焦的模式有自动聚焦、连续自动对焦等
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
setDispaly();
//setCameraDisplayOrientation((Activity) mContext, mCurrentCameraId, mCamera);
camera.setParameters(params);
}
}
/**
* 闪光灯开关 开->关->自动
*/
private void turnLight() {
//mCamera.getParameters().getSupportedFlashModes()获取支持的闪光模式
if (mCamera == null || mCamera.getParameters() == null
|| mCamera.getParameters().getSupportedFlashModes() == null) {
return;
}
Camera.Parameters parameters = mCamera.getParameters();
String flashMode = mCamera.getParameters().getFlashMode();
List<String> supportedModes = mCamera.getParameters()
.getSupportedFlashModes();
//当前是关闭闪光灯
if (Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)
&& supportedModes.contains(Camera.Parameters.FLASH_MODE_ON)) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
mCamera.setParameters(parameters);
flashBtn.setImageResource(R.mipmap.camera_flash_on);
//当前是开启状态
} else if (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)) {
if (supportedModes.contains(Camera.Parameters.FLASH_MODE_AUTO)) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
flashBtn.setImageResource(R.mipmap.camera_flash_auto);
mCamera.setParameters(parameters);
} else if (supportedModes
.contains(Camera.Parameters.FLASH_MODE_OFF)) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
flashBtn.setImageResource(R.mipmap.camera_flash_off);
mCamera.setParameters(parameters);
}
} else if (Camera.Parameters.FLASH_MODE_AUTO.equals(flashMode)
&& supportedModes.contains(Camera.Parameters.FLASH_MODE_OFF)) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
mCamera.setParameters(parameters);
flashBtn.setImageResource(R.mipmap.camera_flash_off);
}
}
4、拍照
public void takePhoto(Camera camera) {
try {
//这里有三个回调接口对象
camera.takePicture(shutterCallback, rawCallback, jpegCallback);
} catch (Throwable t) {
t.printStackTrace();
Toast.makeText(mContext, "拍照失败,请重试!", Toast.LENGTH_LONG)
.show();
try {
camera.startPreview();
} catch (Throwable e) {
}
}
}
//这个就是点击相机的快门调用,一般默认会有咔嚓的声音
Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() {
public void onShutter() {
//可以发声音
}
};
//获取原始数据
Camera.PictureCallback rawCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
}
};
//拍照完成时回调
Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
//保存图片
new SaveImageTask(data,mContext,mCurrentCameraId).execute();
resetCamera(camera);
}
};
5、点击区域聚焦的实现
//当前的Activity中,实现这个方法
//onTouch主要是实现点击进行区域聚焦的功能
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
//将用户手指点击的点的坐标传过去
CameraDoHelper.getInstance(this).pointFocus(motionEvent);
}
} catch (Exception e) {
e.printStackTrace();
}
//这里是为了点击屏幕出现一个聚焦的框
RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(focusIndex.getLayoutParams());
layout.setMargins((int) motionEvent.getX() - 60, (int) motionEvent.getY() - 60, 0,0);
//focusIndex就是一个聚焦的框
focusIndex.setLayoutParams(layout);
focusIndex.setVisibility(View.VISIBLE);
//给聚焦的框一个大小的动画
ScaleAnimation sa = new ScaleAnimation(3f, 1f, 3f, 1f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
sa.setDuration(800);
focusIndex.startAnimation(sa);
handler.postAtTime(new Runnable() {
@Override
public void run() {
focusIndex.setVisibility(View.INVISIBLE);
}
}, 800);
return false;
}
}
//定点对焦的代码
public void pointFocus(MotionEvent event) {
mCamera.cancelAutoFocus();
Camera.Parameters parameters = mCamera.getParameters();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
//showPoint(x, y);
focusOnTouch(event);
}
mCamera.setParameters(parameters);
autoFocus(mCamera);
}
//实现自动对焦
public void autoFocus(final Camera camera) {
new Thread() {
@Override
public void run() {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (camera == null) {
return;
}
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
initCamera(camera,mCurrentCameraId);//实现相机的参数初始化
}
}
});
}
};
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void focusOnTouch(MotionEvent event) {
//这里是计算需要聚焦的区域
Rect focusRect = calculateTapArea(event.getRawX(), event.getRawY(), 1f);
Rect meteringRect = calculateTapArea(event.getRawX(), event.getRawY(), 1.5f);
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
if (parameters.getMaxNumFocusAreas() > 0) {
List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
focusAreas.add(new Camera.Area(focusRect, 1000));
parameters.setFocusAreas(focusAreas);
}
if (parameters.getMaxNumMeteringAreas() > 0) {
List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
meteringAreas.add(new Camera.Area(meteringRect, 1000));
parameters.setMeteringAreas(meteringAreas);
}
mCamera.setParameters(parameters);
mCamera.autoFocus(this);
}
@Override
public void onAutoFocus(boolean success, Camera camera) {
Log.d(TAG, "onAutoFocus : " + success);
}
/**
* Convert touch position x:y to {@link Camera.Area} position -1000:-1000 to 1000:1000.
*/
private Rect calculateTapArea(float x, float y, float coefficient) {
float focusAreaSize = 300;
int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
int centerX = (int) (x / getResolution().width * 2000 - 1000);
int centerY = (int) (y / getResolution().height * 2000 - 1000);
int left = clamp(centerX - areaSize / 2, -1000, 1000);
int right = clamp(left + areaSize, -1000, 1000);
int top = clamp(centerY - areaSize / 2, -1000, 1000);
int bottom = clamp(top + areaSize, -1000, 1000);
return new Rect(left, top, right, bottom);
}
private int clamp(int x, int min, int max) {
if (x > max) {
return max;
}
if (x < min) {
return min;
}
return x;
}
public Camera.Size getResolution() {
Camera.Parameters params = mCamera.getParameters();
Camera.Size s = params.getPreviewSize();
return s;
}
6、保存图片
保存图片其实也有很多的需要考虑的地方,比如,如果我们的相机应用为竖屏应用,这个时候我们预览图像会偏转90度,而且我们排除的图像也会偏转,用户看到的图片会偏转90度的倍数的角度。所以,这个地方需要做一些处理。
public class SaveImageTask extends AsyncTask<Void, Void, String> {
private byte[] data;
private Context mContext;
private GenericProgressDialog mAlertDialog;
private int mCurrentCameraId;
SaveImageTask(byte[] data,Context context,int currentCameraId) {
this.data = data;
mContext = context;
mCurrentCameraId = currentCameraId;
}
@Override
protected String doInBackground(Void... params) {
// Write to SD Card
String path = "";
try {
showProgressDialog("处理中");
path = FileUtil.saveToSDCard(data,mCurrentCameraId);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
return path;
}
@Override
protected void onPostExecute(String path) {
super.onPostExecute(path);
if (!TextUtils.isEmpty(path)) {
Log.d("DemoLog", "path=" + path);
dismissProgressDialog();
} else {
Toast.makeText(mContext, "拍照失败,请稍后重试!",
Toast.LENGTH_LONG).show();
}
}
public class FileUtil {
private static int PHOTO_SIZE_W = 2000;
private static int PHOTO_SIZE_H = 2000;
/**
* 将拍下来的照片存放在SD卡中
*/
public static String saveToSDCard(byte[] data,int currentCameraId) throws IOException {
Bitmap croppedImage;
// 获得图片大小
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
// PHOTO_SIZE = options.outHeight > options.outWidth ? options.outWidth
// : options.outHeight;
PHOTO_SIZE_W = options.outWidth;
PHOTO_SIZE_H = options.outHeight;
options.inJustDecodeBounds = false;
Rect r = new Rect(0, 0, PHOTO_SIZE_W, PHOTO_SIZE_H);
try {
//对图片进行一下修剪
croppedImage = decodeRegionCrop(data, r,currentCameraId);
} catch (Exception e) {
return null;
}
String imagePath = "";
try {
imagePath = saveToFile(croppedImage);
} catch (Exception e) {
}
croppedImage.recycle();
return imagePath;
}
private static Bitmap decodeRegionCrop(byte[] data, Rect rect,int currentCameraId) {
InputStream is = null;
System.gc();
Bitmap croppedImage = null;
try {
is = new ByteArrayInputStream(data);
//BitmapRegionDecoder显示大图片的某一矩形区域
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is,false);
try {
//显示指定的区域
croppedImage = decoder.decodeRegion(rect,
new BitmapFactory.Options());
} catch (IllegalArgumentException e) {
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
}
//进行旋转
Matrix m = new Matrix();
m.setRotate(90, PHOTO_SIZE_W / 2, PHOTO_SIZE_H / 2);
if (currentCameraId == 1) {
//反转图像
m.postScale(1, -1);
}
Bitmap rotatedImage = Bitmap.createBitmap(croppedImage, 0, 0,
PHOTO_SIZE_W, PHOTO_SIZE_H, m, true);
if (rotatedImage != croppedImage)
croppedImage.recycle();
return rotatedImage;
}
// 保存图片文件
public static String saveToFile(Bitmap croppedImage)
throws FileNotFoundException, IOException {
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File(sdCard.getAbsolutePath() + "/DCIM/Camera/");
if (!dir.exists()) {
dir.mkdirs();
}
String fileName = getCameraPath();
File outFile = new File(dir, fileName);
FileOutputStream outputStream = new FileOutputStream(outFile); // 文件输出流
croppedImage.compress(Bitmap.CompressFormat.JPEG, 70, outputStream);
outputStream.flush();
outputStream.close();
return outFile.getAbsolutePath();
}
private static String getCameraPath() {
Calendar calendar = Calendar.getInstance();
StringBuilder sb = new StringBuilder();
sb.append("IMG");
sb.append(calendar.get(Calendar.YEAR));
int month = calendar.get(Calendar.MONTH) + 1; // 0~11
sb.append(month < 10 ? "0" + month : month);
int day = calendar.get(Calendar.DATE);
sb.append(day < 10 ? "0" + day : day);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
sb.append(hour < 10 ? "0" + hour : hour);
int minute = calendar.get(Calendar.MINUTE);
sb.append(minute < 10 ? "0" + minute : minute);
int second = calendar.get(Calendar.SECOND);
sb.append(second < 10 ? "0" + second : second);
if (!new File(sb.toString() + ".jpg").exists()) {
return sb.toString() + ".jpg";
}
StringBuilder tmpSb = new StringBuilder(sb);
int indexStart = sb.length();
for (int i = 1; i < Integer.MAX_VALUE; i++) {
tmpSb.append('(');
tmpSb.append(i);
tmpSb.append(')');
tmpSb.append(".jpg");
if (!new File(tmpSb.toString()).exists()) {
break;
}
tmpSb.delete(indexStart, tmpSb.length());
}
return tmpSb.toString();
}
}
以上就是全部的步骤,下面补全代码:
请看下一篇。