• 玩转Android Camera开发(一):Surfaceview预览Camera,基础拍照功能完整demo


    是在2012年的除夕之夜仓促完成,后来很多人指出了一些问题,琐事缠身一直没有进行升级。后来随着我自己的使用,越来越发现不出个升级版的demo是不行了。有时候就连我自己用这个demo测一些性能、功能点,用着都不顺手。当初代码是在linux下写的,弄到windows里下全是乱码。还要自己改几分钟才能改好。另外,很多人说不能正常预览,原因是我在布局里把Surfaceview的尺寸写死了。再有就是initCamera()的时候设参数失败,直接黑屏退出,原因也是我把预览尺寸和照片尺寸写死了。再有就是照片变形的问题。为此,今天出一个升级版的demo,争取全面适配所有机型。

    上图为此次的代码结构,activity包里就是放CameraActivity,日后添加图库浏览功能再加GalleryActivity。为了使Camera的逻辑和界面的UI耦合度降至最低,封装了CameraInterface类,里面操作Camera的打开、预览、拍照、关闭。preview包里是自定义的Surfaceview。在util包里放着CamParaUtil是专门用来设置、打印Camera的PreviewSize、PictureSize、FocusMode的,并能根据Activity传进来的长宽比(主要是16:9 或 4:3两种尺寸)自动寻找适配的PreviewSize和PictureSize,消除变形。默认的是全屏,因为一些手机全屏时,屏幕的长宽比不是16:9或4:3所以在找尺寸时也是存在一些偏差的。其中有个值,就是判断两个float是否相等,这个参数比较关键,里面设的0.03.经我多个手机测试,这个参数是最合适的,否则的话有些奇葩手机得到的尺寸拍出照片变形。下面上源码:

    一、布局 activity_camera.xml

    <span style="font-family:Comic Sans MS;font-size:18px;"><RelativeLayout 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=".CameraActivity" >
        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
    
            <org.yanzi.camera.preview.CameraSurfaceView
                android:id="@+id/camera_surfaceview"
                android:layout_width="0dip"
                android:layout_height="0dip" />
        </FrameLayout>
    
        <ImageButton
            android:id="@+id/btn_shutter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/btn_shutter_background"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true" 
            android:layout_marginBottom="10dip"/>
    
    
    </RelativeLayout>
    </span>

    二、AndroidManifest.xml

    <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="org.yanzi.playcamera"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="9"
            android:targetSdkVersion="17" />
            <!-- 增加文件存储和访问摄像头的权限 -->
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-feature android:name="android.hardware.camera" />
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher_icon"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name="org.yanzi.activity.CameraActivity"
                android:label="@string/app_name" 
                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
                android:screenOrientation="portrait">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    </span>

    三、下面是java代码

    1、CameraActivity.java

    <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.activity;
    
    import org.yanzi.camera.CameraInterface;
    import org.yanzi.camera.CameraInterface.CamOpenOverCallback;
    import org.yanzi.camera.preview.CameraSurfaceView;
    import org.yanzi.playcamera.R;
    import org.yanzi.util.DisplayUtil;
    
    import android.app.Activity;
    import android.graphics.Point;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.SurfaceHolder;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.ViewGroup.LayoutParams;
    import android.widget.ImageButton;
    
    public class CameraActivity extends Activity implements CamOpenOverCallback {
        private static final String TAG = "yanzi";
        CameraSurfaceView surfaceView = null;
        ImageButton shutterBtn;
        float previewRate = -1f;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Thread openThread = new Thread(){
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    CameraInterface.getInstance().doOpenCamera(CameraActivity.this);
                }
            };
            openThread.start();
            setContentView(R.layout.activity_camera);
            initUI();
            initViewParams();
            
            shutterBtn.setOnClickListener(new BtnListeners());
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.camera, menu);
            return true;
        }
    
        private void initUI(){
            surfaceView = (CameraSurfaceView)findViewById(R.id.camera_surfaceview);
            shutterBtn = (ImageButton)findViewById(R.id.btn_shutter);
        }
        private void initViewParams(){
            LayoutParams params = surfaceView.getLayoutParams();
            Point p = DisplayUtil.getScreenMetrics(this);
            params.width = p.x;
            params.height = p.y;
            previewRate = DisplayUtil.getScreenRate(this); //默认全屏的比例预览
            surfaceView.setLayoutParams(params);
    
            //手动设置拍照ImageButton的大小为120dip×120dip,原图片大小是64×64
            LayoutParams p2 = shutterBtn.getLayoutParams();
            p2.width = DisplayUtil.dip2px(this, 80);
            p2.height = DisplayUtil.dip2px(this, 80);;        
            shutterBtn.setLayoutParams(p2);    
    
        }
    
        @Override
        public void cameraHasOpened() {
            // TODO Auto-generated method stub
            SurfaceHolder holder = surfaceView.getSurfaceHolder();
            CameraInterface.getInstance().doStartPreview(holder, previewRate);
        }
        private class BtnListeners implements OnClickListener{
    
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                switch(v.getId()){
                case R.id.btn_shutter:
                    CameraInterface.getInstance().doTakePicture();
                    break;
                default:break;
                }
            }
    
        }
    
    }
    </span>

    2、CameraInterface.java

    <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.camera;
    
    import java.io.IOException;
    import java.util.List;
    
    import org.yanzi.util.CamParaUtil;
    import org.yanzi.util.FileUtil;
    import org.yanzi.util.ImageUtil;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.PixelFormat;
    import android.hardware.Camera;
    import android.hardware.Camera.PictureCallback;
    import android.hardware.Camera.ShutterCallback;
    import android.hardware.Camera.Size;
    import android.util.Log;
    import android.view.SurfaceHolder;
    
    public class CameraInterface {
        private static final String TAG = "yanzi";
        private Camera mCamera;
        private Camera.Parameters mParams;
        private boolean isPreviewing = false;
        private float mPreviwRate = -1f;
        private static CameraInterface mCameraInterface;
    
        public interface CamOpenOverCallback{
            public void cameraHasOpened();
        }
    
        private CameraInterface(){
    
        }
        public static synchronized CameraInterface getInstance(){
            if(mCameraInterface == null){
                mCameraInterface = new CameraInterface();
            }
            return mCameraInterface;
        }
        /**打开Camera
         * @param callback
         */
        public void doOpenCamera(CamOpenOverCallback callback){
            Log.i(TAG, "Camera open....");
            mCamera = Camera.open();
            Log.i(TAG, "Camera open over....");
            callback.cameraHasOpened();
        }
        /**开启预览
         * @param holder
         * @param previewRate
         */
        public void doStartPreview(SurfaceHolder holder, float previewRate){
            Log.i(TAG, "doStartPreview...");
            if(isPreviewing){
                mCamera.stopPreview();
                return;
            }
            if(mCamera != null){
    
                mParams = mCamera.getParameters();
                mParams.setPictureFormat(PixelFormat.JPEG);//设置拍照后存储的图片格式
                CamParaUtil.getInstance().printSupportPictureSize(mParams);
                CamParaUtil.getInstance().printSupportPreviewSize(mParams);
                //设置PreviewSize和PictureSize
                Size pictureSize = CamParaUtil.getInstance().getPropPictureSize(
                        mParams.getSupportedPictureSizes(),previewRate, 800);
                mParams.setPictureSize(pictureSize.width, pictureSize.height);
                Size previewSize = CamParaUtil.getInstance().getPropPreviewSize(
                        mParams.getSupportedPreviewSizes(), previewRate, 800);
                mParams.setPreviewSize(previewSize.width, previewSize.height);
    
                mCamera.setDisplayOrientation(90);
    
                CamParaUtil.getInstance().printSupportFocusMode(mParams);
                List<String> focusModes = mParams.getSupportedFocusModes();
                if(focusModes.contains("continuous-video")){
                    mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
                }
                mCamera.setParameters(mParams);    
    
                try {
                    mCamera.setPreviewDisplay(holder);
                    mCamera.startPreview();//开启预览
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
                isPreviewing = true;
                mPreviwRate = previewRate;
    
                mParams = mCamera.getParameters(); //重新get一次
                Log.i(TAG, "最终设置:PreviewSize--With = " + mParams.getPreviewSize().width
                        + "Height = " + mParams.getPreviewSize().height);
                Log.i(TAG, "最终设置:PictureSize--With = " + mParams.getPictureSize().width
                        + "Height = " + mParams.getPictureSize().height);
            }
        }
        /**
         * 停止预览,释放Camera
         */
        public void doStopCamera(){
            if(null != mCamera)
            {
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview(); 
                isPreviewing = false; 
                mPreviwRate = -1f;
                mCamera.release();
                mCamera = null;     
            }
        }
        /**
         * 拍照
         */
        public void doTakePicture(){
            if(isPreviewing && (mCamera != null)){
                mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);
            }
        }
    
        /*为了实现拍照的快门声音及拍照保存照片需要下面三个回调变量*/
        ShutterCallback mShutterCallback = new ShutterCallback() 
        //快门按下的回调,在这里我们可以设置类似播放“咔嚓”声之类的操作。默认的就是咔嚓。
        {
            public void onShutter() {
                // TODO Auto-generated method stub
                Log.i(TAG, "myShutterCallback:onShutter...");
            }
        };
        PictureCallback mRawCallback = new PictureCallback() 
        // 拍摄的未压缩原数据的回调,可以为null
        {
    
            public void onPictureTaken(byte[] data, Camera camera) {
                // TODO Auto-generated method stub
                Log.i(TAG, "myRawCallback:onPictureTaken...");
    
            }
        };
        PictureCallback mJpegPictureCallback = new PictureCallback() 
        //对jpeg图像数据的回调,最重要的一个回调
        {
            public void onPictureTaken(byte[] data, Camera camera) {
                // TODO Auto-generated method stub
                Log.i(TAG, "myJpegCallback:onPictureTaken...");
                Bitmap b = null;
                if(null != data){
                    b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字节数据,将其解析成位图
                    mCamera.stopPreview();
                    isPreviewing = false;
                }
                //保存图片到sdcard
                if(null != b)
                {
                    //设置FOCUS_MODE_CONTINUOUS_VIDEO)之后,myParam.set("rotation", 90)失效。
                    //图片竟然不能旋转了,故这里要旋转下
                    Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f);
                    FileUtil.saveBitmap(rotaBitmap);
                }
                //再次进入预览
                mCamera.startPreview();
                isPreviewing = true;
            }
        };
    
    
    }
    </span>

    3、CameraSurfaceView.java

    <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.camera.preview;
    
    import org.yanzi.camera.CameraInterface;
    
    import android.content.Context;
    import android.graphics.PixelFormat;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    
    public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
        private static final String TAG = "yanzi";
        CameraInterface mCameraInterface;
        Context mContext;
        SurfaceHolder mSurfaceHolder;
        public CameraSurfaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
            // TODO Auto-generated constructor stub
            mContext = context;
            mSurfaceHolder = getHolder();
            mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);//translucent半透明 transparent透明
            mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            mSurfaceHolder.addCallback(this);
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            Log.i(TAG, "surfaceCreated...");
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            // TODO Auto-generated method stub
            Log.i(TAG, "surfaceChanged...");
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            Log.i(TAG, "surfaceDestroyed...");
            CameraInterface.getInstance().doStopCamera();
        }
        public SurfaceHolder getSurfaceHolder(){
            return mSurfaceHolder;
        }
        
    }
    </span>

    4、CamParaUtil.java

    <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
    
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    
    import android.hardware.Camera;
    import android.hardware.Camera.Size;
    import android.util.Log;
    
    public class CamParaUtil {
        private static final String TAG = "yanzi";
        private CameraSizeComparator sizeComparator = new CameraSizeComparator();
        private static CamParaUtil myCamPara = null;
        private CamParaUtil(){
    
        }
        public static CamParaUtil getInstance(){
            if(myCamPara == null){
                myCamPara = new CamParaUtil();
                return myCamPara;
            }
            else{
                return myCamPara;
            }
        }
    
        public  Size getPropPreviewSize(List<Camera.Size> list, float th, int minWidth){
            Collections.sort(list, sizeComparator);
    
            int i = 0;
            for(Size s:list){
                if((s.width >= minWidth) && equalRate(s, th)){
                    Log.i(TAG, "PreviewSize:w = " + s.width + "h = " + s.height);
                    break;
                }
                i++;
            }
            if(i == list.size()){
                i = 0;//如果没找到,就选最小的size
            }
            return list.get(i);
        }
        public Size getPropPictureSize(List<Camera.Size> list, float th, int minWidth){
            Collections.sort(list, sizeComparator);
    
            int i = 0;
            for(Size s:list){
                if((s.width >= minWidth) && equalRate(s, th)){
                    Log.i(TAG, "PictureSize : w = " + s.width + "h = " + s.height);
                    break;
                }
                i++;
            }
            if(i == list.size()){
                i = 0;//如果没找到,就选最小的size
            }
            return list.get(i);
        }
    
        public boolean equalRate(Size s, float rate){
            float r = (float)(s.width)/(float)(s.height);
            if(Math.abs(r - rate) <= 0.03)
            {
                return true;
            }
            else{
                return false;
            }
        }
    
        public  class CameraSizeComparator implements Comparator<Camera.Size>{
            public int compare(Size lhs, Size rhs) {
                // TODO Auto-generated method stub
                if(lhs.width == rhs.width){
                    return 0;
                }
                else if(lhs.width > rhs.width){
                    return 1;
                }
                else{
                    return -1;
                }
            }
    
        }
    
        /**打印支持的previewSizes
         * @param params
         */
        public  void printSupportPreviewSize(Camera.Parameters params){
            List<Size> previewSizes = params.getSupportedPreviewSizes();
            for(int i=0; i< previewSizes.size(); i++){
                Size size = previewSizes.get(i);
                Log.i(TAG, "previewSizes:width = "+size.width+" height = "+size.height);
            }
        
        }
    
        /**打印支持的pictureSizes
         * @param params
         */
        public  void printSupportPictureSize(Camera.Parameters params){
            List<Size> pictureSizes = params.getSupportedPictureSizes();
            for(int i=0; i< pictureSizes.size(); i++){
                Size size = pictureSizes.get(i);
                Log.i(TAG, "pictureSizes:width = "+ size.width
                        +" height = " + size.height);
            }
        }
        /**打印支持的聚焦模式
         * @param params
         */
        public void printSupportFocusMode(Camera.Parameters params){
            List<String> focusModes = params.getSupportedFocusModes();
            for(String mode : focusModes){
                Log.i(TAG, "focusModes--" + mode);
            }
        }
    }
    </span>

    5、DisplayUtil.java

    <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
    
    import android.content.Context;
    import android.graphics.Point;
    import android.util.DisplayMetrics;
    import android.util.Log;
    
    public class DisplayUtil {
        private static final String TAG = "DisplayUtil";
        /**
         * dip转px
         * @param context
         * @param dipValue
         * @return
         */
        public static int dip2px(Context context, float dipValue){            
            final float scale = context.getResources().getDisplayMetrics().density;                 
            return (int)(dipValue * scale + 0.5f);         
        }     
        
        /**
         * px转dip
         * @param context
         * @param pxValue
         * @return
         */
        public static int px2dip(Context context, float pxValue){                
            final float scale = context.getResources().getDisplayMetrics().density;                 
            return (int)(pxValue / scale + 0.5f);         
        } 
        
        /**
         * 获取屏幕宽度和高度,单位为px
         * @param context
         * @return
         */
        public static Point getScreenMetrics(Context context){
            DisplayMetrics dm =context.getResources().getDisplayMetrics();
            int w_screen = dm.widthPixels;
            int h_screen = dm.heightPixels;
            Log.i(TAG, "Screen---Width = " + w_screen + " Height = " + h_screen + " densityDpi = " + dm.densityDpi);
            return new Point(w_screen, h_screen);
            
        }
        
        /**
         * 获取屏幕长宽比
         * @param context
         * @return
         */
        public static float getScreenRate(Context context){
            Point P = getScreenMetrics(context);
            float H = P.y;
            float W = P.x;
            return (H/W);
        }
    }
    </span>

    6、FileUtil.java

    <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
    
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    import android.graphics.Bitmap;
    import android.os.Environment;
    import android.util.Log;
    
    public class FileUtil {
        private static final  String TAG = "FileUtil";
        private static final File parentPath = Environment.getExternalStorageDirectory();
        private static   String storagePath = "";
        private static final String DST_FOLDER_NAME = "PlayCamera";
    
        /**初始化保存路径
         * @return
         */
        private static String initPath(){
            if(storagePath.equals("")){
                storagePath = parentPath.getAbsolutePath()+"/" + DST_FOLDER_NAME;
                File f = new File(storagePath);
                if(!f.exists()){
                    f.mkdir();
                }
            }
            return storagePath;
        }
    
        /**保存Bitmap到sdcard
         * @param b
         */
        public static void saveBitmap(Bitmap b){
    
            String path = initPath();
            long dataTake = System.currentTimeMillis();
            String jpegName = path + "/" + dataTake +".jpg";
            Log.i(TAG, "saveBitmap:jpegName = " + jpegName);
            try {
                FileOutputStream fout = new FileOutputStream(jpegName);
                BufferedOutputStream bos = new BufferedOutputStream(fout);
                b.compress(Bitmap.CompressFormat.JPEG, 100, bos);
                bos.flush();
                bos.close();
                Log.i(TAG, "saveBitmap成功");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                Log.i(TAG, "saveBitmap:失败");
                e.printStackTrace();
            }
    
        }
    
    
    }
    </span>

    7、ImageUtil.java

    <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.util;
    
    import android.graphics.Bitmap;
    import android.graphics.Matrix;
    
    public class ImageUtil {
        /**
         * 旋转Bitmap
         * @param b
         * @param rotateDegree
         * @return
         */
        public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree){
            Matrix matrix = new Matrix();
            matrix.postRotate((float)rotateDegree);
            Bitmap rotaBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, false);
            return rotaBitmap;
        }
    }
    </span>

    几点说明:

    1、包括我之前的博文在内的大量网上链接,都是在Surfaceview create的时候进行打开Camera的操作,在Surfaceview Changed的时候进行开预览。而Surfaceview create的时候一定是在setContentView之后,Surfaceview实例化之后。为了优化开启Camera时间,我再setContentView之前new了一个线程专门去Open Camera。经过测试,但就执行Camera.open()这句话一般需要140ms左右。如果放在主线程里无疑是一种浪费。而在140ms之后,Surfaceview里因为无需触发关于Camera的操作,所以加载的特别快。也就是说Open完后,Surfaceview一定完成了实例化。所以我设置了CamOpenOverCallback回调,在Camera打开完毕后通知Activity立即执行开预览的操作。

    2、开预览因为用Surfaceview预览,需传递Surfaceview的SurfaceHolder。

    3、CameraInterface是个单例模式,所有关于Camera的流程性操作一律封装在这里面。

    4、Activity设置了全屏无标题且强制竖屏,像这种操作能再xml写就不要再java代码里弄。

    图片资源上,杂家还真是一番精心挑选,对比了Camera360、相机360、美颜相机,UI上总的来说三个app感觉都很垃圾,都整的太复杂了,图片也不好看。最后勉强用了相机360里的一个button,自己想用PS把按键点击时的图标色彩P亮点,一个没留神,还给P的更暗了色彩。汗,不过按键的对比效果更明显了。日后,会将一些OpenCV4Android的一些小demo都整合到PlayCamera系列。

    下为效果图:

    ------------本文系原创,转载请注明作者yanzi1225627

    版本号:PlayCamera_V1.0.0[2014-6-22].zip

    CSDN下载链接:http://download.csdn.net/detail/yanzi1225627/7540873

  • 相关阅读:
    心跳机制
    有可能出现的中文乱码
    【学习】logger
    【开发效率】Chrome快捷键
    Unable to open debugger port (127.0.0.1:4184): java.net.SocketException "socket closed"
    将项目发布【2.用idea】
    将项目发布【1.用MobaXterm】
    basePath
    大文件上传如何做断点续传?
    JavaScript如何判断一个元素是否在可视区域中?
  • 原文地址:https://www.cnblogs.com/a354823200/p/3998759.html
Copyright © 2020-2023  润新知