首页 > 代码库 > 玩转Android Camera开发(一):Surfaceview预览Camera,基础拍照功能完整demo

玩转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

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