基于SurfaceTexture的静默/无预览拍照方案

来源:互联网 发布:mac project软件 编辑:程序博客网 时间:2024/06/16 07:55

公司业务需要做一个静默拍照的功能,了解了一下常见的解决方案,基本上都是基于SurfaceView做的,弊病颇多。研究了一下,决定以SurfaceTexture为切入点,做一个真正的静默拍照功能。废话不多说,上代码:

package com.xxx.service;import android.app.Service;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.os.RemoteException;import android.text.TextUtils;import android.util.Log;import com.xxx.view.SlientCamera;import org.json.JSONException;import org.json.JSONObject;import java.io.File;import java.lang.ref.WeakReference;/** * * Created by jxl on 2017/8/29. */public class SlientTackPicService extends Service {    private static final String TAG = "slient_tack_pic";    private static final int SLIENT_TACK_CAMERA = 1;    private SlientCamera slientCamera;    private int cameraStatus;    private int replySN;    private String replySEID;    public MyHandler handler = new MyHandler(this);    private static class MyHandler extends Handler {        WeakReference<SlientTackPicService> mContext;        MyHandler(SlientTackPicService mContext) {            this.mContext = new WeakReference<SlientTackPicService>(mContext);        }        @Override        public void handleMessage(Message msg) {            SlientTackPicService service = mContext.get();            if (null == service) {                Log.e(TAG, "mContext.get() is null!");                return;            }            switch (msg.what) {                case SLIENT_TACK_CAMERA:                    Log.i(TAG, "camera ready, tackPicture!");                    service.slientCamera.tackPicture();                    break;                default:                    break;            }        }    }    @Override    public void onCreate() {        super.onCreate();        Log.i(TAG, "SlientTackPicService, onCreate()");        slientCamera = new SlientCamera(this);        slientCamera.onCreate();        cameraStatus = slientCamera.openCamere() ? 1 : -1;        Log.i(TAG, "cameraStatus = " + cameraStatus);        if (cameraStatus == 1) {            handler.sendEmptyMessageDelayed(SLIENT_TACK_CAMERA, 3000);        }    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.i(TAG, "onStartCommand");        return super.onStartCommand(intent, flags, startId);    }    @Override    public IBinder onBind(Intent intent) {        return null;    }    private void releaseSource() {        Log.i(TAG, "releaseSource");        if (handler.hasMessages(SLIENT_TACK_CAMERA)) {            handler.removeMessages(SLIENT_TACK_CAMERA);        }        stopSelf();    }    @Override    public void onDestroy() {        super.onDestroy();        Log.i(TAG, "SlientTackPicService, onDestroy()");        slientCamera.onDestroy();}

SlientTackPicService这个服务在后台运行,主要的拍照操作在 SlientCamera 类中执行。在SlientTackPicService的onCreate()方法中执行对SlientCamera 的初始化,并判断能否打开相机:cameraStatus 为1时表示后台打开相机成功,-1表示打开相机失败。失败原因可能是相机当前正在使用,或者其他未知原因等等。相机打开成功后,延时三秒进行拍照操作。下面上SlientCamera 的代码:

package com.xxx.view;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.ImageFormat;import android.graphics.SurfaceTexture;import android.hardware.Camera;import android.text.TextUtils;import android.util.Log;import com.xxx.service.SlientTackPicService;import com.xxx.tools.BitmapUtil;import com.xxx.tools.CameraUtil;import com.xxx.tools.Constants;import java.io.File;import java.io.IOException;/** * * Created by jxl on 2017/9/1. */public class SlientCamera  implements SurfaceTexture.OnFrameAvailableListener{    private static final String TAG = "slient_camera";    private Context context;    private Camera mCamera;    private SurfaceTexture surfaceTexture;    public SlientCamera(Context context) {        this.context = context;    }    public void onCreate() {        Log.i(TAG, "SlientCamera, onCreate()");        surfaceTexture = new SurfaceTexture(10);        surfaceTexture.setOnFrameAvailableListener(this);        //openCamere();    }    public boolean openCamere() {        try {            mCamera = Camera.open(0);        } catch (Exception e) {            e.printStackTrace();            return false;        }        mCamera.setDisplayOrientation(180);        /*List<Camera.Size> preList = mCamera.getParameters().getSupportedPreviewSizes();        List<Camera.Size> picList = mCamera.getParameters().getSupportedPictureSizes();        for (Camera.Size size:preList) {            Log.i(TAG, "PreviewSize, size.width = " + size.width + ", size.height = " + size.height);        }        for (Camera.Size size:picList) {            Log.i(TAG, "PictureSize, size.width = " + size.width + ", size.height = " + size.height);        }*/        Camera.Parameters params = mCamera.getParameters();        params.setPreviewFormat(ImageFormat.NV21);        //params.setRotation();        /*boolean flag = params.isZoomSupported();        int maxZoom = params.getMaxZoom();*/        params.setZoom(0);//设置焦距为0        params.setPreviewSize(Constants.VIDEO_WIDTH, Constants.VIDEO_HEIGHT);        params.setPictureSize(Constants.PICTURE_WIDTH, Constants.PICTURE_HEIGHT);        mCamera.setParameters(params);        if (mCamera == null) {            // Seeing this on Nexus 7 2012 -- I guess it wants a rear-facing camera, but            // there isn't one.  TODO: fix            //throw new RuntimeException("Default camera not available");            Log.e(TAG, "openCamere, mCamera == null!");            return false;        }        try {            //这一步是最关键的,使用surfaceTexture来承载相机的预览,而不需要设置一个可见的view            mCamera.setPreviewTexture(surfaceTexture);            mCamera.startPreview();        } catch (IOException ioe) {            ioe.printStackTrace();            return false;        }        return true;    }    public void tackPicture() {        //Log.w(TAG, "tackPicture()");        File picFile = CameraUtil.getOutputMediaFile();        if (picFile == null) {            Log.e(TAG, "tackPicture, getOutputMediaFile is null!");            return;        }        if (null == mCamera) {            Log.e(TAG, "tackPicture(), null == mCamera");            return;        }        mCamera.takePicture(mShutterCallback, null, mPictureCallback);        //saveData = true;        //Log.i(TAG, "takePicture after time = " + System.currentTimeMillis());    }    private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {        @Override        public void onShutter() {            //playContinuousSound();        }    };    private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {        @Override        public void onPictureTaken(final byte[] data, Camera camera) {            Log.i(TAG, "data time = " + System.currentTimeMillis());            //bitmap就是拍出来的照片,可以进行需要做的操作            Bitmap bitmap = BitmapUtil.rotaingImageView(0, BitmapFactory.decodeByteArray(data, 0, data.length));            //Log.i(TAG, "save time = " + System.currentTimeMillis());            closeCamera();        }    };    @Override    public void onFrameAvailable(SurfaceTexture surfaceTexture) {        Log.i(TAG, "onFrameAvailable, surfaceTexture.getTimestamp() = " + surfaceTexture.getTimestamp());        //surfaceTexture.updateTexImage();    }    private void closeCamera() {        Log.i(TAG, "closeCamera");        if (null != mCamera) {            mCamera.stopPreview();            mCamera.release();            mCamera = null;        }    }    public void onDestroy() {        closeCamera();    }}

主要代码都在上面了,大概的解释一下:在SlientCamera 的初始化的时候,创建一个SurfaceTexture,这个SurfaceTexture承载了相机的预览。但是由于我们没有设置可见的TextureView,所以不会有预览的界面。关于openCamere这一段,其实是有很多坑的,比如相机前后摄像头、预览角度、预览界面之类的设置。我们公司做的业务是智能硬件,所以硬件参数是固定的,我就把这些参数写死了,实际的应用中是很麻烦的,这里不再赘述了。拍照时调用tackPicture方法,mPictureCallback 方法内的那个byte数组就是最终的照片,将其转化为bitmap即可。具体的实现很简单,就不再贴出来了。
总结一下这个思路,好处在于不需要设置一个可见的view去承载相机的预览,把这个事情交给了surfaceTexture去做,可以全程在后台静默完成,想想还是挺猥琐的。限制在于surfaceTexture只能在API11之上才能调用,而在更高等级的API21中,调用相机需要弹出权限提示框,无法再静默打开摄像头。说白了,这是一个只能在API11~API21中间使用的思路。

阅读全文
1 0