如果有人看這篇文章是新手的話可以先看下相機的基礎(chǔ)知識,可以看下這篇博客:Android: Camera相機開發(fā)詳解(上) —— 知識儲備
先看看效果:

效果.gif
本篇記錄Camera1的機出開發(fā),雖然Camera1在Android中已經(jīng)被廢棄了,但是作為音視頻開發(fā)的切入點,還是有必要了解下的。文章僅供自己記錄用,所以并不會附帶很多文字注釋:
自定義相機開發(fā)的主要流程:
1.創(chuàng)建一個SurfaceView或TextureView的布局用來接收預(yù)覽數(shù)據(jù)
2.獲取SurfaceView的SurfaceHolder并監(jiān)聽Surface的生命周期
3.在Surface創(chuàng)建后開啟相機,設(shè)置相機參數(shù),開啟預(yù)覽(設(shè)置預(yù)覽的旋轉(zhuǎn)方向)
4.拍照(旋轉(zhuǎn)或鏡像旋轉(zhuǎn),保存照片),或切換相機
5.釋放相機資源
下面貼出Camera工具類,每個方法都有注解:
/**
* Camera1的工具類
* 思路:創(chuàng)建SurfaceView或TextureView接收預(yù)覽數(shù)據(jù),
* 獲取SurfaceHolder并監(jiān)聽Surface的生命周期,
* 在Surface創(chuàng)建后開啟相機,設(shè)置相機參數(shù),開啟預(yù)覽(設(shè)置預(yù)覽的旋轉(zhuǎn)方向),
* 拍照(旋轉(zhuǎn)或鏡像旋轉(zhuǎn),保存照片),切換相機,
* 最后釋放相機資源
*/
public class Camera1Helper implements Camera.PreviewCallback, OrientationListener.OnOrientationListener {
private static final String TAG = "CameraHelper";
/**
* 攝像頭方向,默認后置攝像頭
*/
private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
/**
* 預(yù)覽旋轉(zhuǎn)的角度
*/
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;
//當前方向角度監(jiān)聽
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);
}
/**
* 第一步:監(jiān)聽Surface的生命周期
* 開啟相機
* 銷毀相機
*/
public void init() {
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mCamera == null) {
openCamera(); //打開相機
}
startPreview(); //開始預(yù)覽
}
@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);
//獲取支持的預(yù)覽尺寸
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) {
//設(shè)置對焦模式
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// }
//個別機型對攝像頭的預(yù)覽尺寸要求嚴格,需要指定尺寸
setParameter(parameters);
}
/**
* 第四步:開啟預(yù)覽
*/
private void startPreview() {
try {
mCamera.setPreviewDisplay(surfaceHolder);
//設(shè)置預(yù)覽時相機的旋轉(zhuǎn)角度
setCameraDisplayOrientation();
mCamera.startPreview();
isActivedPreview = true;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 第三步:設(shè)置預(yù)覽角度,setDisplayOrientation本身只能改變預(yù)覽的角度
* previewFrameCallback以及拍攝出來的照片是不會發(fā)生改變的,拍攝出來的照片角度依舊不正常的
* 拍攝的照片需要自行處理
* 這里Nexus5X的相機簡直沒法吐槽,后置攝像頭倒置了,切換攝像頭之后就出現(xiàn)問題了。
* 原文鏈接: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
* 指定相機的預(yù)覽尺寸和照片尺寸
*/
private void setParameter(Camera.Parameters parameters) {
try {
//個別機型在SupportPreviewSizes里匯報了支持某種預(yù)覽尺寸,但實際是不支持的,設(shè)置進去就會拋出RuntimeException.
mCamera.setParameters(parameters);
} catch (Exception e) {
e.printStackTrace();
try {
//遇到上面所說的情況,只能設(shè)置一個最小的預(yù)覽尺寸
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;
}
/**
* 獲取最佳的預(yù)覽尺寸
*
* @param width 指定的預(yù)覽寬度
* @param height 指定的預(yù)覽高度
* @param supportedPreviewSizes 相機支持的預(yù)覽尺寸
* @return
*/
private Camera.Size getBestSize(int width, int height, List<Camera.Size> supportedPreviewSizes) {
Camera.Size bestSize = null;
//指定預(yù)覽的寬高比
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;
}
});
}
/**
* 保留兩位小數(shù)
*
* @param o
* @return
*/
private Double format(Double o) {
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(2);
return Double.valueOf(nf.format(o));
}
/**
* 轉(zhuǎn)換對焦區(qū)域
* 范圍(-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;
//計算對焦區(qū)域
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
}
//首先保存原來的對焦模式,然后設(shè)置為macro,對焦回調(diào)后設(shè)置為保存的對焦模式
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) {
//回調(diào)后 還原模式
Camera.Parameters params = camera.getParameters();
params.setFocusMode(currentFocusMode);
camera.setParameters(params);
if (success) {
Toast.makeText(activity, "對焦區(qū)域?qū)钩晒?, 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();
//利用傳感器獲取當前屏幕的角度加上預(yù)覽的角度
int rotation = /*(*/mDisplayOrientation /*+ mSensorRotation) % 360*/;
if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
//如果時后置攝像頭,則直接旋轉(zhuǎn)為特定的角度
matrix.setRotate(rotation);
} else if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//如果時前置攝像頭,鏡像;因為時鏡像,所以旋轉(zhuǎn)角度是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, "圖片數(shù)據(jù)無效");
}
}
}
}).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軸方向加速度,從左側(cè)向右側(cè)移動,values[0]為負值;從右向左移動,values[0]為正值
//y是values[1]的值,Y軸方向加速度,從上到下移動,values[1]為負值;從下往上移動,values[1]為正值
//不考慮Z軸上的數(shù)據(jù),
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;
}
/**
* 在此處接收相機的預(yù)覽數(shù)據(jù)
*
* @param data 相機的預(yù)覽數(shù)據(jù)
* @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中的調(diào)用
@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();
}
}
在設(shè)置Camera的配置的時候,圖像數(shù)據(jù)類型支持多種類型,設(shè)置成parameters.setPreviewFormat(ImageFormat.JPEG);預(yù)覽時輸出的圖像就是JPEG的數(shù)據(jù)
預(yù)覽的圖像數(shù)據(jù)來源于硬件圖像傳感器,圖像傳感器固定在手機上,所以方向是固定的,一般手機的后置攝像頭是橫向固定相對于手持手機豎直方向旋轉(zhuǎn)90度。前置攝像頭剛好相反,獲取的數(shù)據(jù)需要鏡像處理
推薦文章:Android Camera預(yù)覽角度和拍照保存圖片角度學習
Android Orientation Sensor監(jiān)聽方向