android 圆形相机预览拍照_Android Camera SurfaceView 预览拍照

Android使用Camera API + SurfaceView 方式进行预览拍照。

1、创建一个SurfaceView,并实现SurfaceHolder的回调。由于Camera在SurfaceView中是通过SurfaceHolder 使得Surfaceview能够预览Camera返回的数据,因此我们需要实现SurfaceHolder 的回调,实现图如下:

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 的一些操作,比如打开相机、开始预览、停止预览、切换相机、设置预览参数等操作,具体实现如下:

public class CameraUtils {

// 相机默认宽高,相机的宽度和高度跟屏幕坐标不一样,手机屏幕的宽度和高度是反过来的。

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;

/**

* 打开相机,默认打开前置相机

* @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;

}

}

// 如果没有前置摄像头,则打开默认的后置摄像头

if (mCamera == null) {

mCamera = Camera.open();

mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;

}

// 没有摄像头时,抛出异常

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);

}

/**

* 根据ID打开相机

* @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);

}

/**

* 开始预览

* @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();

}

}

/**

* 切换相机

* @param cameraID

*/

public static void switchCamera(int cameraID, SurfaceHolder holder) {

if (mCameraID == cameraID) {

return;

}

mCameraID = cameraID;

// 释放原来的相机

releaseCamera();

// 打开相机

openCamera(cameraID, CameraUtils.DESIRED_PREVIEW_FPS);

// 打开预览

startPreviewDisplay(holder);

}

/**

* 释放相机

*/

public static void releaseCamera() {

if (mCamera != null) {

mCamera.stopPreview();

mCamera.release();

mCamera = null;

}

}

/**

* 开始预览

*/

public static void startPreview() {

if (mCamera != null) {

mCamera.startPreview();

}

}

/**

* 停止预览

*/

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);

}

}

/**

* 设置预览大小

* @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);

}

/**

* 获取预览大小

* @return

*/

public static Camera.Size getPreviewSize() {

if (mCamera != null) {

return mCamera.getParameters().getPreviewSize();

}

return null;

}

/**

* 设置拍摄的照片大小

* @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;

}

/**

* 计算最完美的Size

* @param sizes

* @param expectWidth

* @param expectHeight

* @return

*/

public static Camera.Size calculatePerfectSize(List sizes, int expectWidth,

int expectHeight) {

sortList(sizes); // 根据宽度进行排序

Camera.Size result = sizes.get(0);

boolean widthOrHeight = false; // 判断存在宽或高相等的Size

// 辗转计算宽高最接近的值

for (Camera.Size size: sizes) {

// 如果宽高相等,则直接返回

if (size.width == expectWidth && size.height == expectHeight) {

result = size;

break;

}

// 仅仅是宽度相等,计算高度最接近的size

if (size.width == expectWidth) {

widthOrHeight = true;

if (Math.abs(result.height - expectHeight)

> Math.abs(size.height - expectHeight)) {

result = size;

}

}

// 高度相等,则计算宽度最接近的Size

else if (size.height == expectHeight) {

widthOrHeight = true;

if (Math.abs(result.width - expectWidth)

> Math.abs(size.width - expectWidth)) {

result = size;

}

}

// 如果之前的查找不存在宽或高相等的情况,则计算宽度和高度都最接近的期望值的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 list) {

Collections.sort(list, new Comparator() {

@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 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;

}

/**

* 设置预览角度,setDisplayOrientation本身只能改变预览的角度

* previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的

* 拍摄的照片需要自行处理

* 这里Nexus5X的相机简直没法吐槽,后置摄像头倒置了,切换摄像头之后就出现问题了。

* @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;

}

/**

* 获取当前的Camera ID

* @return

*/

public static int getCameraID() {

return mCameraID;

}

/**

* 获取当前预览的角度

* @return

*/

public static int getPreviewOrientation() {

return mOrientation;

}

/**

* 获取FPS(千秒值)

* @return

*/

public static int getCameraPreviewThousandFps() {

return mCameraPreviewFps;

}

}

3、在Activity中使用CameraSurfaceview,有Android6.0动态权限申请问题,需要我们判断相机和存储权限是否申请了:

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相机动态权限检查

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) {

// 相机权限

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();

}

});

}

/**

* 切换相机

*/

private void switchCamera() {

if (mCameraSurfaceView != null) {

CameraUtils.switchCamera(1 - CameraUtils.getCameraID(), mCameraSurfaceView.getHolder());

// 切换相机后需要重新计算旋转角度

mOrientation = CameraUtils.calculateCameraPreviewOrientation(CameraSurfaceViewActivity.this);

}

}

}

由于用到了相机和存储权限,我们需要在manifest中注册相机和存储权限,这里要说明的是,manifest用use-permission只是声明了需要使用哪些权限,而我们实际项目中在使用到这两项权限时,需要你检查权限是否已经被授权,如果没授权,则需要请求授权:

另外,ImageUtils类的实现如下:

public class ImageUtils {

/**

* 旋转图片

* @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);

}

/**

* 镜像翻转图片

* @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如下:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.cgfay.camerasample.CameraSurfaceViewActivity">

android:id="@+id/layout_aspect"

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="horizontal"

android:layout_gravity="bottom"

android:gravity="center">

android:id="@+id/btn_take"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="拍照" />

android:id="@+id/btn_switch"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="切换相机" />

至此,通过SurfaceView + Camera API 预览拍照功能已经实现。

备注: Camera API 在打开相机是在哪个线程,那么onPreviewFrame回调执行就在哪个线程。因此,如果要通过onPreviewFrame回调使用预览数据,则可以通过HandlerThread 异步调用Camera进行操作。

另外一个问题,onPreviewFrame方法中不要执行过于复杂的逻辑操作,这样会阻塞Camera,无法获取新的Frame,导致帧率下降。

你可能感兴趣的:(android,圆形相机预览拍照)