整合Camera和MediaRecorder实现拍照和录像

来源:互联网 发布:担保交易网站源码 编辑:程序博客网 时间:2024/05/18 03:43

年末最后一篇文章,明天就可以回家过年了.今天带来的效果如下图所示:
这里写图片描述 这里写图片描述

效果图看到的拍照和摄像其实是一个自定义的SurfaceView,在里面实现了拍照和散光灯切换,前后摄像头切换,录像的功能,以及定时录制的功能.下面直接贴出完整代码,关键部分都加上了注释,相信都能看懂的.

public class RecordView extends SurfaceView implements MediaRecorder.OnErrorListener {    private static final String TAG = "RecordView";    private SurfaceHolder mSurfaceHolder;    private int mWidthPixel, mHeightPixel;//SurfaceView的宽高    private int maxDuration;//最大录制时长,单位秒    private Camera mCamera;//相机    private File mRecordFile;//录制的视频文件    private File mCaptureFile;//拍照保存的文件    private Camera.Parameters mCaptureParameters;//相机配置,在录像前记录,用以录像结束后恢复原配置    private MediaRecorder mMediaRecorder;    private FlashMode mFlashMode = FlashMode.AUTO;//闪光灯模式    private int mOrientation;//当前屏幕旋转角度    private int mCurrCameraFacing;//当前设置头类型,0:后置/1:前置    private boolean enableCountDown;//是否启用倒计时录制视频    private CountDownTimer mCountDownTimer;    private boolean isRecording;//是否正在录制    private OnCaptureCallback mOnCaptureCallback;    private OnRecordCallback mOnRecordCallback;    /**     * 录制视频回调     */    public interface OnRecordCallback {        void onFinish(String filePath);        void onProgress(int total, int curr);    }    /**     * 拍照回调     */    public interface OnCaptureCallback {        void onFinish(String filePath);    }    public void setOnCaptureCallback(OnCaptureCallback c) {        this.mOnCaptureCallback = c;    }    public void setOnRecordCallback(OnRecordCallback c) {        this.mOnRecordCallback = c;        enableCountDown = null != c;    }    public void setMaxDuration(int maxDuration) {        this.maxDuration = maxDuration;    }    public RecordView(Context context) {        this(context, null);    }    public RecordView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public RecordView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        mSurfaceHolder = getHolder();        mSurfaceHolder.addCallback(new CustomCallBack());        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);    }    private class CustomCallBack implements SurfaceHolder.Callback {        @Override        public void surfaceCreated(SurfaceHolder holder) {            Log.e(TAG, ">>>>>>>>>>surfaceCreated");            try {                if (mCamera == null) {                    openCamera();                }                if (null != mCamera) {                    mCamera.setPreviewDisplay(mSurfaceHolder);                    mCamera.startPreview();                }            } catch (Exception e) {                e.printStackTrace();                Toast.makeText(getContext(), "打开相机失败", Toast.LENGTH_SHORT).show();            }        }        @Override        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {            Log.e(TAG, ">>>>>>>>>>surfaceChanged width:" + width + " height:" + height);            mWidthPixel = width;            mHeightPixel = height;            setCameraParameters();            updateCameraOrientation();        }        @Override        public void surfaceDestroyed(SurfaceHolder holder) {            Log.e(TAG, ">>>>>>>>>>surfaceDestroyed");            releaseRecord();            releaseCamera();        }    }    /**     * 打开相机     */    private void openCamera() {        if (null != mCamera) {            releaseCamera();        }        try {            if (!checkCameraFacing(0) && !checkCameraFacing(1)) {                Toast.makeText(getContext(), "未发现有可用摄像头", Toast.LENGTH_SHORT).show();                return;            }            if (!checkCameraFacing(mCurrCameraFacing)) {                Toast.makeText(getContext(), mCurrCameraFacing == 0 ? "后置摄像头不可用" : "前置摄像头不可用", Toast.LENGTH_SHORT).show();                return;            }            mCamera = Camera.open(mCurrCameraFacing);        } catch (Exception e) {            e.printStackTrace();            releaseCamera();        }    }    /**     * 设置相机参数     */    private void setCameraParameters() {        if (null != mCamera) {            Camera.Parameters params = mCamera.getParameters();            Camera.Size preViewSize = getOptimalSize(params.getSupportedPreviewSizes(), mWidthPixel, mHeightPixel);            if (null != preViewSize) {                Log.e(TAG, "preViewSize:" + preViewSize.width + " : " + preViewSize.height);                params.setPreviewSize(preViewSize.width, preViewSize.height);            }            Camera.Size pictureSize = getOptimalSize(params.getSupportedPictureSizes(), mWidthPixel, mHeightPixel);            if (null != pictureSize) {                Log.e(TAG, "pictureSize:" + pictureSize.width + " : " + pictureSize.height);                params.setPictureSize(pictureSize.width, pictureSize.height);            }            //设置图片格式            params.setPictureFormat(ImageFormat.JPEG);            params.setJpegQuality(100);            params.setJpegThumbnailQuality(100);            List<String> modes = params.getSupportedFocusModes();            if (modes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {                //支持自动聚焦模式                params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);            }            //设置闪光灯模式            setFlashMode(mFlashMode);            mCamera.setParameters(params);            //开启屏幕朝向监听            startOrientationChangeListener();        }    }    /**     * 初始化摄像     */    private void initRecord() {        try {            if (mMediaRecorder == null)                mMediaRecorder = new MediaRecorder();            else                mMediaRecorder.reset();            mCaptureParameters = mCamera.getParameters();            setFlashMode(FlashMode.OFF);            mCamera.unlock();            mMediaRecorder.setCamera(mCamera);            mMediaRecorder.setOnErrorListener(this);            mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//视频源            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//音频源            mMediaRecorder.setAudioChannels(1);//单声道            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//视频输出格式            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//音频格式            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);//视频录制格式            mMediaRecorder.setVideoSize(320, 240);//设置分辨率,320, 240微信小视频的像素一样            //mMediaRecorder.setVideoFrameRate(17);// 设置每秒帧数 这个设置三星手机会出问题            mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 512);//清晰度            mMediaRecorder.setOrientationHint(90);//输出旋转90度,保持竖屏录制            File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);            if (!dir.exists()) {                dir.mkdirs();            }            mRecordFile = new File(dir, System.currentTimeMillis() + ".mp4");            //mMediaRecorder.setMaxDuration(Constant.MAXVEDIOTIME * 1000);            mMediaRecorder.setOutputFile(mRecordFile.getAbsolutePath());            mMediaRecorder.prepare();        } catch (Exception e) {            e.printStackTrace();            releaseCamera();        }    }    /**     * 设置闪关灯模式     * @param flashMode     */    public void setFlashMode(FlashMode flashMode) {        if (mCamera != null && null != mCamera.getParameters()) {            mFlashMode = flashMode;            Camera.Parameters parameters = mCamera.getParameters();            switch (flashMode) {                case ON:                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);                    break;                case AUTO:                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);                    break;                case TORCH:                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);                    break;                default:                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);                    break;            }            mCamera.setParameters(parameters);        }    }    public FlashMode getFlashMode() {        return mFlashMode;    }    /**     * 切换闪关灯     *     * @return 返回当前闪关灯状态     */    public FlashMode switchFlashMode() {        if (getFlashMode() == FlashMode.ON) {            setFlashMode(FlashMode.OFF);            return FlashMode.OFF;        } else if (getFlashMode() == FlashMode.OFF) {            setFlashMode(FlashMode.AUTO);            return FlashMode.AUTO;        } else if (getFlashMode() == FlashMode.AUTO) {            setFlashMode(FlashMode.TORCH);            return FlashMode.TORCH;        } else if (getFlashMode() == FlashMode.TORCH) {            setFlashMode(FlashMode.ON);            return FlashMode.ON;        }        return null;    }    /**     * 启动屏幕朝向改变监听函数 用于在屏幕横竖屏切换时改变保存的图片的方向     */    private void startOrientationChangeListener() {        OrientationEventListener orientationEventListener = new OrientationEventListener(getContext()) {            @Override            public void onOrientationChanged(int rotation) {                if ((rotation >= 0 && rotation <= 45) || rotation > 315) {                    rotation = 0;                } else if (rotation > 45 && rotation <= 135) {                    rotation = 90;                } else if (rotation > 135 && rotation <= 225) {                    rotation = 180;                } else if (rotation > 225 && rotation <= 315) {                    rotation = 270;                } else {                    rotation = 0;                }                if (rotation == mOrientation) return;                mOrientation = rotation;                updateCameraOrientation();            }        };        orientationEventListener.enable();    }    /**     * 根据当前朝向修改保存图片的旋转角度     */    private void updateCameraOrientation() {        try {            if (mCamera != null && null != mCamera.getParameters()) {                Camera.Parameters parameters = mCamera.getParameters();                //rotation参数为 0、90、180、270。水平方向为0。                int rotation = 90 + mOrientation == 360 ? 0 : 90 + mOrientation;                //前置摄像头需要对垂直方向做变换,否则照片是颠倒的                if (mCurrCameraFacing == 1) {                    if (rotation == 90) rotation = 270;                    else if (rotation == 270) rotation = 90;                }                parameters.setRotation(rotation);//生成的图片转90°                //预览图片旋转90°                mCamera.setDisplayOrientation(90);//预览转90°                mCamera.setParameters(parameters);            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 获取最优尺寸     * @param sizes     * @param w     * @param h     * @return     */    private Camera.Size getOptimalSize(List<Camera.Size> sizes, int w, int h) {        final double ASPECT_TOLERANCE = 0.1;        double targetRatio = (double) h / w;        if (w > h)            targetRatio = (double) w / h;        if (sizes == null)            return null;        Camera.Size optimalSize = null;        double minDiff = Double.MAX_VALUE;        int targetHeight = h;        for (Camera.Size size : sizes) {            double ratio = (double) size.width / size.height;            if (size.height >= size.width)                ratio = (float) size.height / size.width;            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)                continue;            if (Math.abs(size.height - targetHeight) < minDiff) {                optimalSize = size;                minDiff = Math.abs(size.height - targetHeight);            }        }        if (optimalSize == null) {            minDiff = Double.MAX_VALUE;            for (Camera.Size size : sizes) {                if (Math.abs(size.height - targetHeight) < minDiff) {                    optimalSize = size;                    minDiff = Math.abs(size.height - targetHeight);                }                Log.d(TAG, "getOptimalSize: width:" + size.width + " height:" + size.height + " minDiff:" + minDiff);            }        }        return optimalSize;    }    /**     * 检查是否有摄像头     *     * @param facing 前置还是后置     * @return     */    private boolean checkCameraFacing(int facing) {        int cameraCount = Camera.getNumberOfCameras();        Camera.CameraInfo info = new Camera.CameraInfo();        for (int i = 0; i < cameraCount; i++) {            Camera.getCameraInfo(i, info);            if (facing == info.facing) {                return true;            }        }        return false;    }    /**     * 切换摄像头     */    public void switchCamera() {        if (mCurrCameraFacing == 0) {            mCurrCameraFacing = Camera.CameraInfo.CAMERA_FACING_FRONT;        } else {            mCurrCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;        }        if(null !=mMediaRecorder && isRecording) stopRecord();        openCamera();        if (mCamera != null) {            setCameraParameters();            updateCameraOrientation();            try {                mCamera.setPreviewDisplay(getHolder());                mCamera.startPreview();            } catch (IOException e) {                e.printStackTrace();            }        }    }    /**     * 停止录制     */    public void stopRecord() {        endTimeCount();        releaseRecord();        if (isRecording && null != mOnRecordCallback && null != mRecordFile && !TextUtils.isEmpty(mRecordFile.getPath())) {            mOnRecordCallback.onFinish(mRecordFile.getPath());        }        isRecording = false;    }    /**     * 开始录制     */    public void startRecord() {        if (mCamera == null)            openCamera();        if (mCamera == null) {            return;        }        mCamera.autoFocus(null);        initRecord();        if (null != mMediaRecorder) {            mMediaRecorder.start();//开始录制            isRecording = true;            if (enableCountDown) {                startTimeCount();            }        }    }    /**     * 开始拍照     */    public void startCapture() {        if (null != mCamera) {            mCamera.autoFocus(new Camera.AutoFocusCallback() {                @Override                public void onAutoFocus(boolean success, Camera camera) {                    camera.takePicture(null, null, pictureCallback);                }            });        }    }    private final Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {        @Override        public void onPictureTaken(byte[] data, Camera camera) {            if (data != null) {                //解析生成相机返回的图片                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);                //生成缩略图                // Bitmap thumbnail= ThumbnailUtils.extractThumbnail(bm, 213, 213);                try {                    File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);                    if (!dir.exists()) {                        dir.mkdirs();                    }                    mCaptureFile = new File(dir, System.currentTimeMillis() + ".jpg");                    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(mCaptureFile));                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);                    bos.flush();                    bos.close();                    if (null != mOnCaptureCallback) {                        mOnCaptureCallback.onFinish(mCaptureFile.getPath());                    }                } catch (Exception e) {                    e.printStackTrace();                    Toast.makeText(getContext(), "保存相片失败", Toast.LENGTH_SHORT).show();                } finally {                    saveRotatePicture(mCaptureFile.getPath());                }            } else {                Toast.makeText(getContext(), "拍照失败,请重试", Toast.LENGTH_SHORT).show();            }            //重新打开预览图,进行下一次的拍照准备            camera.startPreview();        }    };    private void saveRotatePicture(String path) {        if (TextUtils.isEmpty(path)) return;        int degree = getBitmapDegree(path);        rotateBitmapByDegree(BitmapFactory.decodeFile(path), degree);    }    /**     * 读取图片的旋转的角度     *     * @param path 图片绝对路径     * @return 图片的旋转角度     */    private int getBitmapDegree(String path) {        int degree = 0;        try {            // 从指定路径下读取图片,并获取其EXIF信息            ExifInterface exifInterface = new ExifInterface(path);            // 获取图片的旋转信息            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);            switch (orientation) {                case ExifInterface.ORIENTATION_ROTATE_90:                    degree = 90;                    break;                case ExifInterface.ORIENTATION_ROTATE_180:                    degree = 180;                    break;                case ExifInterface.ORIENTATION_ROTATE_270:                    degree = 270;                    break;            }        } catch (IOException e) {            e.printStackTrace();        }        Log.d(TAG, "degree:" + degree);        return degree;    }    /**     * 将图片按照某个角度进行旋转     *     * @param bm     需要旋转的图片     * @param degree 旋转角度     */    private void rotateBitmapByDegree(Bitmap bm, int degree) {        Bitmap newBitmap = null;        // 根据旋转角度,生成旋转矩阵        Matrix matrix = new Matrix();        matrix.postRotate(degree);        try {            // 将原始图片按照旋转矩阵进行旋转,并得到新的图片            newBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);            File rotateFile = new File(mCaptureFile.getPath().replace(".jpg", "_rotate.jpg"));            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(rotateFile));            newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);            bos.flush();            bos.close();        } catch (Exception e) {            e.printStackTrace();        } finally {            if (newBitmap == null) {                newBitmap = bm;            }            if (bm != newBitmap) {                bm.recycle();            }        }    }    /**     * 开始计时     */    private void startTimeCount() {        endTimeCount();        mCountDownTimer = new CountDownTimer(maxDuration * 1000l+1000l, 1000l) {            @Override            public void onTick(long millisUntilFinished) {                if (null != mOnRecordCallback) {                    mOnRecordCallback.onProgress(maxDuration, (int) (maxDuration - millisUntilFinished / 1000l));                }                Log.e(TAG, "onTick:" + (millisUntilFinished / 1000l));            }            @Override            public void onFinish() {                Log.e(TAG, "onFinish");                if (null != mOnRecordCallback) {                    mOnRecordCallback.onProgress(maxDuration, maxDuration);                }                stopRecord();            }        }.start();    }    /**     * 结束倒计时     */    private void endTimeCount() {        if (null != mCountDownTimer) {            mCountDownTimer.cancel();            mCountDownTimer = null;        }    }    /**     * 释放录像资源     */    private void releaseRecord() {        try {            if (null != mMediaRecorder) {                mMediaRecorder.setOnErrorListener(null);                if (isRecording) mMediaRecorder.stop();                mMediaRecorder.reset();                mMediaRecorder.release();                mMediaRecorder = null;                // TODO: 2017/1/22 保存视频的缩略图                //恢复相机参数                if (mCaptureParameters != null && mCamera != null) {                    //重新连接相机                    mCamera.reconnect();                    //停止预览,注意这里必须先调用停止预览再设置参数才有效                    mCamera.stopPreview();                    //设置参数为录像前的参数,不然如果录像是低配,结束录制后预览效果还是低配画面                    mCamera.setParameters(mCaptureParameters);                    //重新打开                    mCamera.startPreview();                    mCaptureParameters = null;                }            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 释放相机资源     */    private void releaseCamera() {        if (null != mCamera) {            try {                mCamera.stopPreview();                mCamera.release();                mCamera = null;            } catch (Exception e) {                e.printStackTrace();            }        }    }    @Override    public void onError(MediaRecorder mr, int what, int extra) {        try {            if (mr != null)                mr.reset();            isRecording = false;            Toast.makeText(getContext(), "视频录制出错,请重试", Toast.LENGTH_SHORT).show();        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 返回录像文件     *     * @return     */    public File getRecordFile() {        return mRecordFile;    }    /**     * 返回拍照文件     *     * @return     */    public File getCaptureFile() {        return mCaptureFile;    }    /**     * 返回录像文件路径     *     * @return recordFile     */    public String getRecordFilePath() {        return null == mRecordFile ? "" : mRecordFile.getPath();    }    /**     * 返回拍照文件路径     *     * @return     */    public String getCaptureFilePath() {        return null == mCaptureFile ? "" : mCaptureFile.getPath();    }    /**     * 获取旋转后的图片路径     * @return     */    public String getRotateCaptureFilePath() {        return getCaptureFilePath().replace(".jpg", "_rotate.jpg");    }    /**     * 闪光灯类型枚举 默认为关闭     */    public enum FlashMode {        /**         * ON:拍照时打开闪光灯         */        ON,        /**         * OFF:不打开闪光灯         */        OFF,        /**         * AUTO:系统决定是否打开闪光灯         */        AUTO,        /**         * TORCH:一直打开闪光灯         */        TORCH    }}

上面这个类是最核心的部分,由于demo文件比较多,下面直接给出demo下载地址.
有2点疑问,暂时没有解决.如有知道的朋友望解答下
1.三星手机拍照出来的相片,预览的时候是倒着的,尽管我在代码中做了角度判断和旋转操作,依然没用.而我用华为手机预览是正常的.
2.如何实现在录像过程中切换摄像头继续录制.由于没有想到合适的方法,切换过程中我是直接结束录制的.

demo下载

0 0
原创粉丝点击