美文网首页Camera想关
Android Camera SurfaceView 预览拍照

Android Camera SurfaceView 预览拍照

作者: cain_huang | 来源:发表于2017-07-09 01:41 被阅读1551次

    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<Camera.Size> 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<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;
        }
    
        /**
         * 设置预览角度,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只是声明了需要使用哪些权限,而我们实际项目中在使用到这两项权限时,需要你检查权限是否已经被授权,如果没授权,则需要请求授权:

    <!-- 存储权限 -->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <!-- 相机权限 -->
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <uses-permission android:name="android.permission.FLASHLIGHT" />
    

    另外,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如下:

    <?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="切换相机" />
        </LinearLayout>
    
    </FrameLayout>
    

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

    备注: Camera API 在打开相机是在哪个线程,那么onPreviewFrame回调执行就在哪个线程。因此,如果要通过onPreviewFrame回调使用预览数据,则可以通过HandlerThread 异步调用Camera进行操作。
    另外一个问题,onPreviewFrame方法中不要执行过于复杂的逻辑操作,这样会阻塞Camera,无法获取新的Frame,导致帧率下降。

    相关文章

      网友评论

      • 55921475ddc3:java.lang.RuntimeException: Fail to connect to camera service
        at com.example.camerashow.CameraUtils.openFrontalCamera(CameraUtils.java:41)
        at com.example.camerashow.CameraSurfaceView.surfaceCreated(CameraSurfaceView.java:44)
        你好,按照你的方法出现了这样的错误了,是什么问题啊?
        55921475ddc3:@cain_huang mBtnTake = (Button) findViewById(R.id.btn_take);
        mBtnTake.setOnClickListener(this);
        mBtnSwitch = (Button) findViewById(R.id.btn_switch);
        mBtnSwitch.setOnClickListener(this);
        这两个button需要做实现东西吗?
        55921475ddc3:@cain_huang <uses-permission android:name="android.permission.CAMERA"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.WAKE_LOCK"/>

        <uses-feature
        android:name="android.hardware.camera"
        android:required="false"/>
        <uses-feature
        android:name="android.hardware.camera.front"
        android:required="false"/>
        <uses-feature
        android:name="android.hardware.camera.autofocus"
        android:required="false"/>
        <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true"/>
        你好,这些加进去还是出错。
        cain_huang:@自由自在_0165 你开了权限了没?Android6.0需要在原来Manifest添加了相机features的基础上,再添加相机的动态权限申请,这两样东西缺一不可。要不然就会提示failed to connect to camera service的错误。这个RuntimeException就是动态权限抛出的异常。
      • 899e2d74bdeb:请问Camera API 在打开相机是在哪个线程,那么onPreviewFrame回调执行就在哪个线程?我在子线程上open Camera 但是onPreviewFrame回调还是在主线程上。这跟你说的不一致。
        代码如下, new Thread(new Runnable() {
        @Override
        public void run() {
        mCamera = Camera.open(mCameraId);
        }
        }).start();
        是不是该线程没有绑定Looper所造成的。
        cain_huang:@哎疯 是的。new Thread().start() 这样的方式新开一个线程,该Thread绑定的Looper还是主线程的Looper,也就是MainLooper。这样操作的话,onPreviewFrame还是会掉到主线程上的,底层是根据Looper来判断的。应用层的Camera API 通过Binder驱动调用了底层的Camera,但是传递过去的只有Looper的信息,如果当前的Thread 没有指定具体的Looper,则绑定的是MainLooper。因此,你这样的写法是有问题的。你可以使用Thread 来自定义Looper,也可以使用HandlerThread来打开Camera,我个人比较喜欢使用HandlerThread。写法也挺方便的,就是 new一个 HandlerThread, 然后调用 handlerThread.start()启动一个线程,然后创建一个Handler,并绑定当前的HandlerThread的Looper。最后在打开Camera的使用使用handler.post(new Runnable() { @Override
        pubic void run() {
        mCamera = Camera.open(mCameraId);
        }
        });
        这样回调后,onPreviewFrame回调的线程就不在是MianLooper,而是绑定的HandlerThread所在的Looper了。或者你可以参考一下我写的相机项目:
        https://github.com/CainKernel/CainCamera
        我把相机控制和渲染线程封装得比较好了,也比那些开源相机项目封装得要好很多,并且onPreviewFrame回调帧率在同样是做磨皮的情况下,要比市面上几乎所有美颜相机的帧率要好。另外一个问题需要你注意的就是,相机控制逻辑和渲染在两个不同的HandlerThread的上在不同的CPU上,onPreviewFrame的帧率是不一样的,在MTK的CPU上,onPreviewFrame回调帧率只有相机控制逻辑和渲染都在同一个HandlerThread的情况帧率的一般左右。详情请参考本人的两篇文章:《关于Android Camera onPreviewFrame 预览回调帧率问题》
        (http://www.jianshu.com/p/a33b1eabe71c)
        《Android预览实时渲染的帧率优化相关》(http://www.jianshu.com/p/7187fe10261d)
      • 歌缅过往:activity中ImageUtils.getRotatedBitmap可以发出来吗
        cain_huang:@歌缅过往 代码很简单,就是下面的代码。如果你有实时渲染的需求,可以参考本人的项目:https://github.com/CainKernel/CainCamera ,项目最近做了比较大的改动,目目前经过磨皮后的预览帧率,能够跟市面上的相机项目一比高下

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

      本文标题:Android Camera SurfaceView 预览拍照

      本文链接:https://www.haomeiwen.com/subject/gvsjhxtx.html