Android使用Camera API + SurfaceView 方式進(jìn)行預(yù)覽拍照蒲每。
1净薛、創(chuàng)建一個(gè)SurfaceView瘩例,并實(shí)現(xiàn)SurfaceHolder的回調(diào)矢劲。由于Camera在SurfaceView中是通過SurfaceHolder 使得Surfaceview能夠預(yù)覽Camera返回的數(shù)據(jù)赦拘,因此我們需要實(shí)現(xiàn)SurfaceHolder 的回調(diào),實(shí)現(xiàn)圖如下:
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = CameraSurfaceView.class.getSimpleName();
private SurfaceHolder mSurfaceHolder;
public CameraSurfaceView(Context context) {
super(context);
init();
}
public CameraSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
CameraUtils.openFrontalCamera(CameraUtils.DESIRED_PREVIEW_FPS);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
CameraUtils.startPreviewDisplay(holder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
CameraUtils.releaseCamera();
}
}
2芬沉、CameraUtils 輔助類主要是Camera API 的一些操作躺同,比如打開相機(jī)、開始預(yù)覽丸逸、停止預(yù)覽蹋艺、切換相機(jī)、設(shè)置預(yù)覽參數(shù)等操作椭员,具體實(shí)現(xiàn)如下:
public class CameraUtils {
// 相機(jī)默認(rèn)寬高车海,相機(jī)的寬度和高度跟屏幕坐標(biāo)不一樣,手機(jī)屏幕的寬度和高度是反過來的隘击。
public static final int DEFAULT_WIDTH = 1280;
public static final int DEFAULT_HEIGHT = 720;
public static final int DESIRED_PREVIEW_FPS = 30;
private static int mCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT;
private static Camera mCamera;
private static int mCameraPreviewFps;
private static int mOrientation = 0;
/**
* 打開相機(jī)侍芝,默認(rèn)打開前置相機(jī)
* @param expectFps
*/
public static void openFrontalCamera(int expectFps) {
if (mCamera != null) {
throw new RuntimeException("camera already initialized!");
}
Camera.CameraInfo info = new Camera.CameraInfo();
int numCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numCameras; i++) {
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
mCamera = Camera.open(i);
mCameraID = info.facing;
break;
}
}
// 如果沒有前置攝像頭,則打開默認(rèn)的后置攝像頭
if (mCamera == null) {
mCamera = Camera.open();
mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;
}
// 沒有攝像頭時(shí)埋同,拋出異常
if (mCamera == null) {
throw new RuntimeException("Unable to open camera");
}
Camera.Parameters parameters = mCamera.getParameters();
mCameraPreviewFps = CameraUtils.chooseFixedPreviewFps(parameters, expectFps * 1000);
parameters.setRecordingHint(true);
mCamera.setParameters(parameters);
setPreviewSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);
setPictureSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);
mCamera.setDisplayOrientation(mOrientation);
}
/**
* 根據(jù)ID打開相機(jī)
* @param cameraID
* @param expectFps
*/
public static void openCamera(int cameraID, int expectFps) {
if (mCamera != null) {
throw new RuntimeException("camera already initialized!");
}
mCamera = Camera.open(cameraID);
if (mCamera == null) {
throw new RuntimeException("Unable to open camera");
}
mCameraID = cameraID;
Camera.Parameters parameters = mCamera.getParameters();
mCameraPreviewFps = CameraUtils.chooseFixedPreviewFps(parameters, expectFps * 1000);
parameters.setRecordingHint(true);
mCamera.setParameters(parameters);
setPreviewSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);
setPictureSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);
mCamera.setDisplayOrientation(mOrientation);
}
/**
* 開始預(yù)覽
* @param holder
*/
public static void startPreviewDisplay(SurfaceHolder holder) {
if (mCamera == null) {
throw new IllegalStateException("Camera must be set when start preview");
}
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 切換相機(jī)
* @param cameraID
*/
public static void switchCamera(int cameraID, SurfaceHolder holder) {
if (mCameraID == cameraID) {
return;
}
mCameraID = cameraID;
// 釋放原來的相機(jī)
releaseCamera();
// 打開相機(jī)
openCamera(cameraID, CameraUtils.DESIRED_PREVIEW_FPS);
// 打開預(yù)覽
startPreviewDisplay(holder);
}
/**
* 釋放相機(jī)
*/
public static void releaseCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
/**
* 開始預(yù)覽
*/
public static void startPreview() {
if (mCamera != null) {
mCamera.startPreview();
}
}
/**
* 停止預(yù)覽
*/
public static void stopPreview() {
if (mCamera != null) {
mCamera.stopPreview();
}
}
/**
* 拍照
*/
public static void takePicture(Camera.ShutterCallback shutterCallback,
Camera.PictureCallback rawCallback,
Camera.PictureCallback pictureCallback) {
if (mCamera != null) {
mCamera.takePicture(shutterCallback, rawCallback, pictureCallback);
}
}
/**
* 設(shè)置預(yù)覽大小
* @param camera
* @param expectWidth
* @param expectHeight
*/
public static void setPreviewSize(Camera camera, int expectWidth, int expectHeight) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = calculatePerfectSize(parameters.getSupportedPreviewSizes(),
expectWidth, expectHeight);
parameters.setPreviewSize(size.width, size.height);
camera.setParameters(parameters);
}
/**
* 獲取預(yù)覽大小
* @return
*/
public static Camera.Size getPreviewSize() {
if (mCamera != null) {
return mCamera.getParameters().getPreviewSize();
}
return null;
}
/**
* 設(shè)置拍攝的照片大小
* @param camera
* @param expectWidth
* @param expectHeight
*/
public static void setPictureSize(Camera camera, int expectWidth, int expectHeight) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = calculatePerfectSize(parameters.getSupportedPictureSizes(),
expectWidth, expectHeight);
parameters.setPictureSize(size.width, size.height);
camera.setParameters(parameters);
}
/**
* 獲取照片大小
* @return
*/
public static Camera.Size getPictureSize() {
if (mCamera != null) {
return mCamera.getParameters().getPictureSize();
}
return null;
}
/**
* 計(jì)算最完美的Size
* @param sizes
* @param expectWidth
* @param expectHeight
* @return
*/
public static Camera.Size calculatePerfectSize(List<Camera.Size> sizes, int expectWidth,
int expectHeight) {
sortList(sizes); // 根據(jù)寬度進(jìn)行排序
Camera.Size result = sizes.get(0);
boolean widthOrHeight = false; // 判斷存在寬或高相等的Size
// 輾轉(zhuǎn)計(jì)算寬高最接近的值
for (Camera.Size size: sizes) {
// 如果寬高相等州叠,則直接返回
if (size.width == expectWidth && size.height == expectHeight) {
result = size;
break;
}
// 僅僅是寬度相等,計(jì)算高度最接近的size
if (size.width == expectWidth) {
widthOrHeight = true;
if (Math.abs(result.height - expectHeight)
> Math.abs(size.height - expectHeight)) {
result = size;
}
}
// 高度相等凶赁,則計(jì)算寬度最接近的Size
else if (size.height == expectHeight) {
widthOrHeight = true;
if (Math.abs(result.width - expectWidth)
> Math.abs(size.width - expectWidth)) {
result = size;
}
}
// 如果之前的查找不存在寬或高相等的情況咧栗,則計(jì)算寬度和高度都最接近的期望值的Size
else if (!widthOrHeight) {
if (Math.abs(result.width - expectWidth)
> Math.abs(size.width - expectWidth)
&& Math.abs(result.height - expectHeight)
> Math.abs(size.height - expectHeight)) {
result = size;
}
}
}
return result;
}
/**
* 排序
* @param list
*/
private static void sortList(List<Camera.Size> list) {
Collections.sort(list, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size pre, Camera.Size after) {
if (pre.width > after.width) {
return 1;
} else if (pre.width < after.width) {
return -1;
}
return 0;
}
});
}
/**
* 選擇合適的FPS
* @param parameters
* @param expectedThoudandFps 期望的FPS
* @return
*/
public static int chooseFixedPreviewFps(Camera.Parameters parameters, int expectedThoudandFps) {
List<int[]> supportedFps = parameters.getSupportedPreviewFpsRange();
for (int[] entry : supportedFps) {
if (entry[0] == entry[1] && entry[0] == expectedThoudandFps) {
parameters.setPreviewFpsRange(entry[0], entry[1]);
return entry[0];
}
}
int[] temp = new int[2];
int guess;
parameters.getPreviewFpsRange(temp);
if (temp[0] == temp[1]) {
guess = temp[0];
} else {
guess = temp[1] / 2;
}
return guess;
}
/**
* 設(shè)置預(yù)覽角度,setDisplayOrientation本身只能改變預(yù)覽的角度
* previewFrameCallback以及拍攝出來的照片是不會(huì)發(fā)生改變的虱肄,拍攝出來的照片角度依舊不正常的
* 拍攝的照片需要自行處理
* 這里Nexus5X的相機(jī)簡(jiǎn)直沒法吐槽致板,后置攝像頭倒置了,切換攝像頭之后就出現(xiàn)問題了咏窿。
* @param activity
*/
public static int calculateCameraPreviewOrientation(Activity activity) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraID, 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;
}
mOrientation = result;
return result;
}
/**
* 獲取當(dāng)前的Camera ID
* @return
*/
public static int getCameraID() {
return mCameraID;
}
/**
* 獲取當(dāng)前預(yù)覽的角度
* @return
*/
public static int getPreviewOrientation() {
return mOrientation;
}
/**
* 獲取FPS(千秒值)
* @return
*/
public static int getCameraPreviewThousandFps() {
return mCameraPreviewFps;
}
}
3斟或、在Activity中使用CameraSurfaceview,有Android6.0動(dòng)態(tài)權(quán)限申請(qǐng)問題集嵌,需要我們判斷相機(jī)和存儲(chǔ)權(quán)限是否申請(qǐng)了:
public class CameraSurfaceViewActivity extends AppCompatActivity implements View.OnClickListener {
private static final int REQUEST_CAMERA = 0x01;
private CameraSurfaceView mCameraSurfaceView;
private Button mBtnTake;
private Button mBtnSwitch;
private int mOrientation;
// CameraSurfaceView 容器包裝類
private FrameLayout mAspectLayout;
private boolean mCameraRequested;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_camera_surface);
// Android 6.0相機(jī)動(dòng)態(tài)權(quán)限檢查
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
initView();
} else {
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, REQUEST_CAMERA);
}
}
/**
* 初始化View
*/
private void initView() {
mAspectLayout = (FrameLayout) findViewById(R.id.layout_aspect);;
mCameraSurfaceView = new CameraSurfaceView(this);
mAspectLayout.addView(mCameraSurfaceView);
mOrientation = CameraUtils.calculateCameraPreviewOrientation(CameraSurfaceViewActivity.this);
mBtnTake = (Button) findViewById(R.id.btn_take);
mBtnTake.setOnClickListener(this);
mBtnSwitch = (Button) findViewById(R.id.btn_switch);
mBtnSwitch.setOnClickListener(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
// 相機(jī)權(quán)限
case REQUEST_CAMERA:
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mCameraRequested = true;
initView();
}
break;
}
}
@Override
protected void onResume() {
super.onResume();
if (mCameraRequested) {
CameraUtils.startPreview();
}
}
@Override
protected void onPause() {
super.onPause();
CameraUtils.stopPreview();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_take:
takePicture();
break;
case R.id.btn_switch:
switchCamera();
break;
}
}
/**
* 拍照
*/
private void takePicture() {
CameraUtils.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
}, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
CameraUtils.startPreview();
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
if (bitmap != null) {
bitmap = ImageUtils.getRotatedBitmap(bitmap, mOrientation);
String path = Environment.getExternalStorageDirectory() + "/DCIM/Camera/"
+ System.currentTimeMillis() + ".jpg";
try {
FileOutputStream fout = new FileOutputStream(path);
BufferedOutputStream bos = new BufferedOutputStream(fout);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.flush();
bos.close();
fout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
CameraUtils.startPreview();
}
});
}
/**
* 切換相機(jī)
*/
private void switchCamera() {
if (mCameraSurfaceView != null) {
CameraUtils.switchCamera(1 - CameraUtils.getCameraID(), mCameraSurfaceView.getHolder());
// 切換相機(jī)后需要重新計(jì)算旋轉(zhuǎn)角度
mOrientation = CameraUtils.calculateCameraPreviewOrientation(CameraSurfaceViewActivity.this);
}
}
}
由于用到了相機(jī)和存儲(chǔ)權(quán)限萝挤,我們需要在manifest中注冊(cè)相機(jī)和存儲(chǔ)權(quán)限御毅,這里要說明的是,manifest用use-permission只是聲明了需要使用哪些權(quán)限怜珍,而我們實(shí)際項(xiàng)目中在使用到這兩項(xiàng)權(quán)限時(shí)端蛆,需要你檢查權(quán)限是否已經(jīng)被授權(quán),如果沒授權(quán)酥泛,則需要請(qǐng)求授權(quán):
<!-- 存儲(chǔ)權(quán)限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 相機(jī)權(quán)限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
另外今豆,ImageUtils類的實(shí)現(xiàn)如下:
public class ImageUtils {
/**
* 旋轉(zhuǎn)圖片
* @param bitmap
* @param rotation
* @Return
*/
public static Bitmap getRotatedBitmap(Bitmap bitmap, int rotation) {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
}
/**
* 鏡像翻轉(zhuǎn)圖片
* @param bitmap
* @Return
*/
public static Bitmap getFlipBitmap(Bitmap bitmap) {
Matrix matrix = new Matrix();
matrix.setScale(-1, 1);
matrix.postTranslate(bitmap.getWidth(), 0);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
}
}
layout如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.cgfay.camerasample.CameraSurfaceViewActivity">
<FrameLayout
android:id="@+id/layout_aspect"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="bottom"
android:gravity="center">
<Button
android:id="@+id/btn_take"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照" />
<Button
android:id="@+id/btn_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="切換相機(jī)" />
</LinearLayout>
</FrameLayout>
至此,通過SurfaceView + Camera API 預(yù)覽拍照功能已經(jīng)實(shí)現(xiàn)揭璃。
備注: Camera API 在打開相機(jī)是在哪個(gè)線程晚凿,那么onPreviewFrame回調(diào)執(zhí)行就在哪個(gè)線程。因此瘦馍,如果要通過onPreviewFrame回調(diào)使用預(yù)覽數(shù)據(jù)歼秽,則可以通過HandlerThread 異步調(diào)用Camera進(jìn)行操作。
另外一個(gè)問題情组,onPreviewFrame方法中不要執(zhí)行過于復(fù)雜的邏輯操作燥筷,這樣會(huì)阻塞Camera,無法獲取新的Frame院崇,導(dǎo)致幀率下降肆氓。