如果有人看这篇文章是新手的话可以先看下相机的基础知识,可以看下这篇博客:Android: Camera相机开发详解(上) —— 知识储备
先看看效果:
本篇记录Camera1的机出开发,虽然Camera1在Android中已经被废弃了,但是作为音视频开发的切入点,还是有必要了解下的。文章仅供自己记录用,所以并不会附带很多文字注释:
自定义相机开发的主要流程:
1.创建一个SurfaceView或TextureView的布局用来接收预览数据
2.获取SurfaceView的SurfaceHolder并监听Surface的生命周期
3.在Surface创建后开启相机,设置相机参数,开启预览(设置预览的旋转方向)
4.拍照(旋转或镜像旋转,保存照片),或切换相机
5.释放相机资源
下面贴出Camera工具类,每个方法都有注解:
/**
* Camera1的工具类
* 思路:创建SurfaceView或TextureView接收预览数据,
* 获取SurfaceHolder并监听Surface的生命周期,
* 在Surface创建后开启相机,设置相机参数,开启预览(设置预览的旋转方向),
* 拍照(旋转或镜像旋转,保存照片),切换相机,
* 最后释放相机资源
*/
public class Camera1Helper implements Camera.PreviewCallback, OrientationListener.OnOrientationListener {
private static final String TAG = "CameraHelper";
/**
* 摄像头方向,默认后置摄像头
*/
private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
/**
* 预览旋转的角度
*/
private int mDisplayOrientation = 0;
private int useWidth;
private int useHeight;
private Activity activity;
private MySurfaceView surfaceView;
private final SurfaceHolder surfaceHolder;
private Camera mCamera;
private final Point point;
private int mSensorRotation;
private boolean isActivedPreview = false;
//当前方向角度监听
private OrientationListener orientationListener;
public Camera1Helper(Activity activity, MySurfaceView surfaceView) {
this.activity = activity;
this.surfaceView = surfaceView;
surfaceHolder = surfaceView.getHolder();
point = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(point);
useHeight = point.y;
useWidth = point.x;
Log.i(TAG, "屏幕的尺寸:" + useWidth + " " + useHeight);
acceleRometer();
init();
}
/**
* 开启传感器加速
*/
public void acceleRometer() {
orientationListener = new OrientationListener(activity);
orientationListener.registerListener();
orientationListener.setOnOrientationListener(this);
}
/**
* 第一步:监听Surface的生命周期
* 开启相机
* 销毁相机
*/
public void init() {
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mCamera == null) {
openCamera(); //打开相机
}
startPreview(); //开始预览
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
}
});
}
/**
* 第二步:打开相机
*/
public void openCamera() {
mCamera = Camera.open(mCameraFacing);
initCameraConfig();
mCamera.setPreviewCallback(this);
}
/**
* 配置相机的信息
*/
private void initCameraConfig() {
if (surfaceView != null) {
surfaceView.setCustomTouchEvent(new MySurfaceView.CustomTouchEvent() {
@Override
public void onTouchEvent(MotionEvent event) {
handleFocus(event, mCamera);
}
});
}
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRecordingHint(true);
parameters.setAutoWhiteBalanceLock(true);
parameters.setPreviewFormat(ImageFormat.JPEG);
//获取支持的预览尺寸
List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
//获取与指定宽高相近的尺寸
Camera.Size bestSize = getBestSize(useWidth, useHeight, supportedPreviewSizes);
parameters.setPreviewSize(bestSize.width, bestSize.height);
parameters.setPictureSize(bestSize.width, bestSize.height);
//判断当前相机是否支持当前的对焦模式
boolean supportedFocusModes = isSupportedFocusModes(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
parameters.getSupportedFocusModes());
// if (supportedFocusModes) {
//设置对焦模式
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// }
//个别机型对摄像头的预览尺寸要求严格,需要指定尺寸
setParameter(parameters);
}
/**
* 第四步:开启预览
*/
private void startPreview() {
try {
mCamera.setPreviewDisplay(surfaceHolder);
//设置预览时相机的旋转角度
setCameraDisplayOrientation();
mCamera.startPreview();
isActivedPreview = true;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 第三步:设置预览角度,setDisplayOrientation本身只能改变预览的角度
* previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的
* 拍摄的照片需要自行处理
* 这里Nexus5X的相机简直没法吐槽,后置摄像头倒置了,切换摄像头之后就出现问题了。
* 原文链接:https://blog.csdn.net/u010126792/article/details/86529646
*/
private void setCameraDisplayOrientation() {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraFacing, 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;
}
mDisplayOrientation = result;
mCamera.setDisplayOrientation(mDisplayOrientation);
System.out.println("=========orienttaion=============" + result);
}
/**
* 最后:释放相机资源
*/
public void releaseCamera() {
if (mCamera != null) {
orientationListener.unregisterListener();
mCamera.stopPreview();
// surfaceHolder.removeCallback(surfaceHolderCallBack);
mCamera.setPreviewCallback(null);
mCamera.lock();
mCamera.release();
isActivedPreview = false;
mCamera = null;
}
}
/**
* Camera.setParameter
* 指定相机的预览尺寸和照片尺寸
*/
private void setParameter(Camera.Parameters parameters) {
try {
//个别机型在SupportPreviewSizes里汇报了支持某种预览尺寸,但实际是不支持的,设置进去就会抛出RuntimeException.
mCamera.setParameters(parameters);
} catch (Exception e) {
e.printStackTrace();
try {
//遇到上面所说的情况,只能设置一个最小的预览尺寸
parameters.setPreviewSize(1920, 1080);
mCamera.setParameters(parameters);
} catch (Exception ex) {
ex.printStackTrace();
try {
parameters.setPictureSize(1920, 1080);
mCamera.setParameters(parameters);
} catch (Exception ignored) {
}
}
}
}
/**
* 判断当前的相机是否支持当前的对焦模式
*
* @param focusModeContinuousPicture 对焦模式
* @param supportedFocusModes 支持的对焦模式
*/
private boolean isSupportedFocusModes(String focusModeContinuousPicture,
List<String> supportedFocusModes) {
for (String supportedFocusMode : supportedFocusModes) {
if (supportedFocusMode.equals(focusModeContinuousPicture))
return true;
}
return false;
}
/**
* 获取最佳的预览尺寸
*
* @param width 指定的预览宽度
* @param height 指定的预览高度
* @param supportedPreviewSizes 相机支持的预览尺寸
* @return
*/
private Camera.Size getBestSize(int width, int height, List<Camera.Size> supportedPreviewSizes) {
Camera.Size bestSize = null;
//指定预览的宽高比
Double rate = (double) height / (double) width;
Log.i(TAG, " format:" + rate);
List<Camera.Size> maxSizeList = new ArrayList<>();
List<Camera.Size> minSizeList = new ArrayList<>();
for (Camera.Size size : supportedPreviewSizes) {
if (width == size.width && height == size.height) {
return size;
}
Double i = (double) size.width / (double) size.height;
if (i > rate) {
maxSizeList.add(size);
} else {
minSizeList.add(size);
}
}
sortSizeList(maxSizeList);
sortSizeList(minSizeList);
Camera.Size size1 = bestSize(point, maxSizeList);
Camera.Size size2 = bestSize(point, minSizeList);
if (size1.width > size2.width) {
bestSize = size1;
} else {
bestSize = size2;
}
Log.i(TAG, "目标尺寸:width:" + width + " * height" + height);
Log.i(TAG, "最佳尺寸:width:" + bestSize.width + " * height:" + bestSize.height);
return bestSize;
}
private Camera.Size bestSize(Point point, List<Camera.Size> list) {
ArrayMap<Integer, Camera.Size> map = new ArrayMap<>();
List<Integer> list1 = new ArrayList<>();
for (Camera.Size size1 : list) {
int abs = Math.abs(point.x - size1.width);
list1.add(abs);
map.put(abs, size1);
}
sortList(list1);
return map.get(list1.get(list1.size() - 1));
}
private void sortList(List<Integer> list) {
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 > o2) {
return 1;
} else if (o1 < o2) {
return -1;
}
return 0;
}
});
}
private void sortSizeList(List<Camera.Size> list) {
Collections.sort(list, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size o1, Camera.Size o2) {
if (o1.width > o2.width) {
return 1;
} else if (o1.width < o2.width) {
return -1;
}
return 0;
}
});
}
/**
* 保留两位小数
*
* @param o
* @return
*/
private Double format(Double o) {
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
return Double.valueOf(nf.format(o));
}
/**
* 转换对焦区域
* 范围(-1000, -1000, 1000, 1000)
*/
private Rect calculateTapArea(float x, float y, int width, int height, float coefficient) {
float focusAreaSize = 200;
int areaSize = (int) (focusAreaSize * coefficient);
int surfaceWidth = width;
int surfaceHeight = height;
int centerX = (int) (x / surfaceHeight * 2000 - 1000);
int centerY = (int) (y / surfaceWidth * 2000 - 1000);
int left = clamp(centerX - (areaSize / 2), -1000, 1000);
int top = clamp(centerY - (areaSize / 2), -1000, 1000);
int right = clamp(left + areaSize, -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;
}
private void handleFocus(MotionEvent event, Camera camera) {
int viewWidth = useWidth;
int viewHeight = useHeight;
//计算对焦区域
Rect focusRect = calculateTapArea(event.getX(), event.getY(),
viewWidth, viewHeight, 1.0f);
//一定要首先取消
camera.cancelAutoFocus();
Camera.Parameters params = camera.getParameters();
if (params.getMaxNumFocusAreas() > 0) {
List<Camera.Area> focusAreas = new ArrayList<>();
focusAreas.add(new Camera.Area(focusRect, 800));
params.setFocusAreas(focusAreas);
} else {
//focus areas not supported
}
//首先保存原来的对焦模式,然后设置为macro,对焦回调后设置为保存的对焦模式
final String currentFocusMode = params.getFocusMode();
params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
setParameter(params);
if (isActivedPreview) {
camera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
//回调后 还原模式
Camera.Parameters params = camera.getParameters();
params.setFocusMode(currentFocusMode);
camera.setParameters(params);
if (success) {
Toast.makeText(activity, "对焦区域对焦成功", Toast.LENGTH_SHORT).show();
}
}
});
}
}
/**
* 切换摄像头
*/
public void switchCamera() {
releaseCamera();
if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
mCameraFacing = Camera.CameraInfo.CAMERA_FACING_FRONT;
} else {
mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
}
openCamera();
startPreview();
}
/**
* 拍摄照片
*/
public void takePhoto() {
mCamera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.i(TAG, "线程:" + Thread.currentThread().getName());
camera.startPreview();
new Thread(new Runnable() {
@Override
public void run() {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
String dirPath = activity.getExternalFilesDir(
Environment.DIRECTORY_PICTURES).getPath() + "/focus/";
File dirFile = new File(dirPath);
if (!dirFile.exists())
dirFile.mkdirs();
String picturePath = dirPath + "picture.jpeg";
File picFile = new File(picturePath);
if (picFile.exists())
picFile.delete();
//给图片变换为视觉角度
if (data != null && data.length > 0) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
//利用传感器获取当前屏幕的角度加上预览的角度
int rotation = /*(*/mDisplayOrientation /*+ mSensorRotation) % 360*/;
if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
//如果时后置摄像头,则直接旋转为特定的角度
matrix.setRotate(rotation);
} else if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//如果时前置摄像头,镜像;因为时镜像,所以旋转角度是360-rotation
rotation = (360 - rotation) % 360;
matrix.setRotate(rotation);
matrix.postScale(-1, 1);
}
Bitmap bitmap1 = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
//存储图片
saveBmp(bitmap1, picFile);
} else {
Log.i(TAG, "图片数据无效");
}
}
}
}).start();
}
});
}
/**
* 存储图片
*/
private void saveBmp(Bitmap bmp, File picFile) {
try {
FileOutputStream fos = new FileOutputStream(picFile);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "图片保存成功", Toast.LENGTH_SHORT).show();
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
bmp.recycle();
}
}
/**
* 计算传感器的方向
* https://blog.csdn.net/u010126792/article/details/86706199
*/
private int calculateSensorRotation(float x, float y) {
//x是values[0]的值,X轴方向加速度,从左侧向右侧移动,values[0]为负值;从右向左移动,values[0]为正值
//y是values[1]的值,Y轴方向加速度,从上到下移动,values[1]为负值;从下往上移动,values[1]为正值
//不考虑Z轴上的数据,
if (Math.abs(x) > 6 && Math.abs(y) < 4) {
if (x > 6) {
return 270;
} else {
return 90;
}
} else if (Math.abs(y) > 6 && Math.abs(x) < 4) {
if (y > 6) {
return 0;
} else {
return 180;
}
}
return -1;
}
/**
* 在此处接收相机的预览数据
*
* @param data 相机的预览数据
* @param camera 相机
*/
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
@Override
public void onOrientationChanged(float azimuth, float pitch, float roll) {
Log.d(TAG, "航向角:" + azimuth + "--俯仰角:" + pitch + "--翻滚角:" + roll);
mSensorRotation = (int) azimuth;
}
}
Activity中的调用
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
surfaceView = view.findViewById(R.id.surfaceView);
switchCamera = view.findViewById(R.id.switchCamera);
takePhoto = view.findViewById(R.id.takePhoto);
if (camera1Helper == null)
camera1Helper = new Camera1Helper(Objects.requireNonNull(getActivity()), surfaceView);
switchCamera.setOnClickListener(this);
takePhoto.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.switchCamera) {
camera1Helper.switchCamera();
} else if (v.getId() == R.id.takePhoto) {
camera1Helper.takePhoto();
}
}
在设置Camera的配置的时候,图像数据类型支持多种类型,设置成parameters.setPreviewFormat(ImageFormat.JPEG);预览时输出的图像就是JPEG的数据
预览的图像数据来源于硬件图像传感器,图像传感器固定在手机上,所以方向是固定的,一般手机的后置摄像头是横向固定相对于手持手机竖直方向旋转90度。前置摄像头刚好相反,获取的数据需要镜像处理
推荐文章:Android Camera预览角度和拍照保存图片角度学习
Android Orientation Sensor监听方向