android Camera 开发
2020-12-01 本文已影响0人
谦谦行者
注:下面例子中,scanView是一个自定义view,背景透明,需要放在预览的中间位置,否则自动对焦不准,设置对焦区域还不会
自定义相机拍照
本文参考:https://juejin.cn/post/6844903534610087943#heading-3
在android自定义camera需要根据android版本使用不同的api
如下图:

在对应版本使用对应api,才能兼容大多数设备
另外,与上图不同的是,使用CameraX 替代 Camera2。
CameraX 拥有比Camera2更好的兼容性,但是最低支持api21 所以在android 21 之前仍然使用Camera1
Camera1+SurfaceView
- 获取摄像头Camera1对象
- 设置 SurfaceHolder.addCallback
- 在回调的 surfaceCreated() 中设置camera 参数,调用camera.startPreview() 开启预览
- 拍照
1.获取摄像头Camera1对象
val numberOfCameras = Camera.getNumberOfCameras()
for (index in 0 until numberOfCameras) {
val cameraInfo = Camera.CameraInfo()
Camera.getCameraInfo(index, cameraInfo)
// 前置摄像头
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
frontCameraId = index
frontCameraOrientation = cameraInfo.orientation
// 后置摄像头
}else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
backCameraId = index
backCameraOrientation = cameraInfo.orientation
}
}
2. 设置 SurfaceHolder.addCallback
sv.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder?) {
camera = Camera.open(backCameraId)
// 设置摄像头参数,开启预览
startPreview(sv.holder)
}
override fun surfaceChanged(
holder: SurfaceHolder?,
format: Int,
width: Int,
height: Int,
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
})
3. 设置camera参数,开启预览
fun startPreview(surfaceHolder: SurfaceHolder){
// 获取相机参数
val params = camera.parameters
// 设置自动对焦
params.focusMode = Camera.Parameters.FOCUS_MODE_AUTO // 自动对焦
// 设置闪光模式,自动模式
params.flashMode = Camera.Parameters.FLASH_MODE_AUTO // 自动模式,当光线较暗时自动打开闪光灯
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = windowManager.defaultDisplay
val screenResolution = getDisplaySize(display)
// 获取手机最佳预览尺寸
val point = CameraConfigurationUtils.findBestPreviewSizeValue(params, screenResolution)
isRotate = point.x > point.y
val rotation = windowManager.defaultDisplay.rotation
var degrees = 0
when (rotation) {
Surface.ROTATION_0 -> {
degrees = 0
}
Surface.ROTATION_90 -> {
degrees = 90
}
Surface.ROTATION_180 -> {
degrees = 180
}
Surface.ROTATION_270 -> {
degrees = 270
}
}
val previewSizes = params.supportedPictureSizes
previewSizes.forEach {
Log.e("预览尺寸", "width:${it.width} height:${it.height}")
}
// 设置预览方向(垂直)
camera.setDisplayOrientation((backCameraOrientation - degrees + 360) % 360)
params.setPreviewSize(point.x, point.y)
camera.parameters = params
camera.setPreviewDisplay(surfaceHolder)
camera.startPreview()
// 只有加上下面的代码才能自动对焦,只在param设置自动对焦是没用的
camera.autoFocus { success, camera ->
if (success) {
camera.cancelAutoFocus()//只有加上了这一句,才会自动对焦
doAutoFocus()
}
}
}
//设置聚焦
private fun doAutoFocus() {
var parameters = camera.parameters
parameters.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
camera.parameters = parameters
camera.autoFocus(AutoFocusCallback { success, camera ->
if (success) {
camera.cancelAutoFocus() // 只有加上了这一句,才会自动对焦。
if (Build.MODEL != "KORIDY H30") {
parameters = camera.parameters
parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE // 连续自动对焦
camera.parameters = parameters
} else {
parameters = camera.parameters
parameters.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
camera.parameters = parameters
}
}
})
}
// 获取当前手机屏幕的尺寸
private fun getDisplaySize(display: Display): Point {
val point = Point()
try {
display.getSize(point)
} catch (ignore: NoSuchMethodError) {
point.x = display.width
point.y = display.height
}
return point
}
获取预览最佳尺寸的工具类
public final class CameraConfigurationUtils {
private static final String TAG = "CameraConfiguration";
private static final int MIN_PREVIEW_PIXELS = 480 * 320; // normal screen
private static final double MAX_ASPECT_DISTORTION = 0.15;
public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {
List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
if (rawSupportedSizes == null) {
Log.w(TAG, "Device returned no supported preview sizes; using default");
Camera.Size defaultSize = parameters.getPreviewSize();
if (defaultSize == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
return new Point(defaultSize.width, defaultSize.height);
}
if (Log.isLoggable(TAG, Log.INFO)) {
StringBuilder previewSizesString = new StringBuilder();
for (Camera.Size size : rawSupportedSizes) {
previewSizesString.append(size.width).append('x').append(size.height).append(' ');
}
Log.i(TAG, "Supported preview sizes: " + previewSizesString);
}
// double screenAspectRatio = screenResolution.x / (double) screenResolution.y;
// Find a suitable size, with max resolution
// int maxResolution = 0;
Camera.Size maxResPreviewSize = null;
int diff = Integer.MAX_VALUE;
for (Camera.Size size : rawSupportedSizes) {
int realWidth = size.width;
int realHeight = size.height;
int resolution = realWidth * realHeight;
if (resolution < MIN_PREVIEW_PIXELS) {
continue;
}
boolean isCandidatePortrait = realWidth < realHeight;
int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
// double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;
// double distortion = Math.abs(aspectRatio - screenAspectRatio);
// if (distortion > MAX_ASPECT_DISTORTION) {
// continue;
// }
if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
Point exactPoint = new Point(realWidth, realHeight);
Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
return exactPoint;
}
int newDiff = Math.abs(maybeFlippedWidth - screenResolution.x) + Math.abs(maybeFlippedHeight - screenResolution.y);
if (newDiff < diff) {
maxResPreviewSize = size;
diff = newDiff;
}
// Resolution is suitable; record the one with max resolution
// if (resolution > maxResolution) {
// maxResolution = resolution;
// maxResPreviewSize = size;
// }
}
// If no exact match, use largest preview size. This was not a great idea on older devices because
// of the additional computation needed. We're likely to get here on newer Android 4+ devices, where
// the CPU is much more powerful.
if (maxResPreviewSize != null) {
Point largestSize = new Point(maxResPreviewSize.width, maxResPreviewSize.height);
Log.i(TAG, "Using largest suitable preview size: " + largestSize);
return largestSize;
}
// If there is nothing at all suitable, return current preview size
Camera.Size defaultPreview = parameters.getPreviewSize();
if (defaultPreview == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
return defaultSize;
}
}
4.拍照
调用camera.takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback jpeg)
只实现第三个回调即可
camera.takePicture(null, null, { data, camera ->
// 创建存放照片的文件
val file =
File("${getExternalStorageDirectory().path}/${System.currentTimeMillis()}test_photo.jpg")
val result = FileUtils.createFileByDeleteOldFile(file)
// Tools的getFocusedBitmap 是根据传入的Rect,截取对应区域的照片生成一个bitmap
// scan_view 是一个自定义view,画了一个扫描框,注意获取getRect()时
// 要穿scan_view 在相对于父布局的位置
val bitmap = Tools.getFocusedBitmap(this, camera, data, scan_view.getRect())
// 把图片存在文件里
FileIOUtils.writeFileFromBytesByStream(file, ImageUtils.bitmap2Bytes(bitmap,
Bitmap.CompressFormat.JPEG))
camera?.startPreview()
})
Tools工具类
public class Tools {
public static Bitmap rotateBitmap(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}
public static Bitmap preRotateBitmap(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.preRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, false);
}
public enum ScalingLogic {
CROP, FIT
}
public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
if (scalingLogic == ScalingLogic.FIT) {
final float srcAspect = (float) srcWidth / (float) srcHeight;
final float dstAspect = (float) dstWidth / (float) dstHeight;
if (srcAspect > dstAspect) {
return srcWidth / dstWidth;
} else {
return srcHeight / dstHeight;
}
} else {
final float srcAspect = (float) srcWidth / (float) srcHeight;
final float dstAspect = (float) dstWidth / (float) dstHeight;
if (srcAspect > dstAspect) {
return srcHeight / dstHeight;
} else {
return srcWidth / dstWidth;
}
}
}
public static Bitmap decodeByteArray(byte[] bytes, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth,
dstHeight, scalingLogic);
Bitmap unscaledBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
return unscaledBitmap;
}
public static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
if (scalingLogic == ScalingLogic.CROP) {
final float srcAspect = (float) srcWidth / (float) srcHeight;
final float dstAspect = (float) dstWidth / (float) dstHeight;
if (srcAspect > dstAspect) {
final int srcRectWidth = (int) (srcHeight * dstAspect);
final int srcRectLeft = (srcWidth - srcRectWidth) / 2;
return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight);
} else {
final int srcRectHeight = (int) (srcWidth / dstAspect);
final int scrRectTop = (int) (srcHeight - srcRectHeight) / 2;
return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight);
}
} else {
return new Rect(0, 0, srcWidth, srcHeight);
}
}
public static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
if (scalingLogic == ScalingLogic.FIT) {
final float srcAspect = (float) srcWidth / (float) srcHeight;
final float dstAspect = (float) dstWidth / (float) dstHeight;
if (srcAspect > dstAspect) {
return new Rect(0, 0, dstWidth, (int) (dstWidth / srcAspect));
} else {
return new Rect(0, 0, (int) (dstHeight * srcAspect), dstHeight);
}
} else {
return new Rect(0, 0, dstWidth, dstHeight);
}
}
public static Bitmap createScaledBitmap(Bitmap unscaledBitmap, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
dstWidth, dstHeight, scalingLogic);
Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
dstWidth, dstHeight, scalingLogic);
Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(scaledBitmap);
canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG));
return scaledBitmap;
}
public static Point getScreenResolution(Context context) {
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
return new Point(width, height);
}
// 根据相机拍照原图,生成符合Rect尺寸的压缩过的图片
public static Bitmap getFocusedBitmap(Context context, Camera camera, byte[] data, Rect box){
Point ScrRes = getScreenResolution(context);
Point CamRes = CameraConfigurationUtils.findBestPreviewSizeValue(camera.getParameters(), ScrRes);
int SW = ScrRes.x;
int SH = ScrRes.y;
int RW = box.width();
int RH = box.height();
int RL = box.left;
int RT = box.top;
float RSW = (float) (RW * Math.pow(SW, -1));
float RSH = (float) (RH * Math.pow(SH, -1));
float RSL = (float) (RL * Math.pow(SW, -1));
float RST = (float) (RT * Math.pow(SH, -1));
float k = 0.5f;
int CW = CamRes.x;
int CH = CamRes.y;
int X = (int) (k * CW);
int Y = (int) (k * CH);
Bitmap unscaledBitmap = Tools.decodeByteArray(data, X, Y, ScalingLogic.CROP);
// 压缩图片,会降低清晰度
Bitmap bmp = Tools.createScaledBitmap(unscaledBitmap, X, Y, ScalingLogic.CROP);
unscaledBitmap.recycle();
if (CW > CH){
bmp = Tools.rotateBitmap(bmp, 90);
}else {
String phone = Build.BRAND + Build.MODEL;
if(phone.equals("RedmiM2004J7AC")){
bmp = Tools.rotateBitmap(bmp, 90);
}
}
int BW = bmp.getWidth();
int BH = bmp.getHeight();
int RBL = (int) (RSL * BW);
int RBT = (int) (RST * BH);
int RBW = (int) (RSW * BW);
int RBH = (int) (RSH * BH);
Bitmap res = Bitmap.createBitmap(bmp, RBL, RBT, RBW, RBH);
bmp.recycle();
return res;
}
// private static Pattern pattern = Pattern.compile("(1|861)\\d{10}$*");
private static Pattern pattern = Pattern.compile("[0-9]{13}[D-F]");
private static StringBuilder bf = new StringBuilder();
public static String getTelNum(String sParam){
if(TextUtils.isEmpty(sParam)){
return "";
}
Matcher matcher = pattern.matcher(sParam.trim());
bf.delete(0, bf.length());
Log.e("test", sParam);
while (matcher.find()) {
bf.append(matcher.group()).append("\n");
}
int len = bf.length();
if (len > 0) {
bf.deleteCharAt(len - 1);
}
Log.e("test", bf.toString());
return bf.toString();
}
}
Camera1+TextureView
与SurfaceView不同的是,需要设置TextureView.surfaceTextureListener
在onSurfaceTextureAvailable() 中初始化设置camera 的参数
并且调用camera.setPreviewTexture(surfaceTexture) 替换camera.setPreviewDisplay(surfaceHolder)
最后在不用的时候,调用camera.stopPreview()、camera.release() 停止预览和释放相机资源