美文网首页IS移动开发总结
Android 实现自定义相机

Android 实现自定义相机

作者: cao健强 | 来源:发表于2017-03-26 14:54 被阅读638次

    摘要: 基本上所有应用都会用到相机功能,上周尝试了下自定义的相机功能实现。下面记录下在学习尝试中的一些心得。

    相机的启动方式有两种:
    1、利用隐式跳转跳转到系统的相机应用

             Intent intent = new Intent();  
             intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);  
             startActivity(intent);
    

    2、利用camera类自定义相机界面
    自定义相机的一般步骤:
    1. 创建显示相机画面的布局,Android已经为我们选定好SurfaceView
    2. 通过SurfaceView#getHolder()获得链接Camera和SurfaceView的SurfaceHolder
    3. Camame.open()打开相机
    4. 通过SurfaceHolder链接Camera和SurfaceView实现预览
    5. 进行拍照或录制

    3、代码演示

    private void init() {
            TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.MovieRecorderView, defStyle, 0);
            mWidth = a.getInteger(R.styleable.MovieRecorderView_width, 320);// 默认320
            mHeight = a.getInteger(R.styleable.MovieRecorderView_height, 240);// 默认240
    
            isOpenCamera = a.getBoolean(
                    R.styleable.MovieRecorderView_is_open_camera, true);// 默认打开
            mSurfaceView = (SurfaceView) findViewById(R.id.surfaceview);
            mSurfaceHolder = mSurfaceView.getHolder();
            mSurfaceHolder.addCallback(new CustomCallBack());
            mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
    
        private class CustomCallBack implements Callback {
    
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (!isOpenCamera)
                    return;
                try {
                    initCamera();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width,
                    int height) {
    
            }
    
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if (!isOpenCamera)
                    return;
                freeCameraResource();
            }
    
        }
    
        /**
         * 初始化摄像头同时开启预览
         */
        private void initCamera() throws IOException {
            if (mCamera != null) {
                freeCameraResource();
            }
            try {
                mCamera = Camera.open();
            } catch (Exception e) {
                e.printStackTrace();
                freeCameraResource();
            }
            if (mCamera == null)
                return;
    
    //      setCameraParams();
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mSurfaceHolder);
            
            DisplayMetrics dm = new DisplayMetrics();    
            dm = getResources().getDisplayMetrics();  
            setCameraParams(mCamera,dm.widthPixels,dm.heightPixels);
            
            mCamera.startPreview();
            mCamera.unlock();
        }
    
        /**
         * 释放摄像头资源
         */
        private void freeCameraResource() {
            if (mCamera != null) {
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview();
    //          mCamera.lock();
                mCamera.release();
                mCamera = null;
                isOpenCamera = false;
            }
        }
    
        /**
         * 释放资源
         */
        private void releaseRecord() {
            if (mMediaRecorder != null) {
                mMediaRecorder.setOnErrorListener(null);
                try {
                    mMediaRecorder.release();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            mMediaRecorder = null;
        }
    
    

    接下来时设置照片尺寸放置预览时的图像变形

        private void setCameraParams(Camera camera, int width, int height) {
            Log.i(TAG,"setCameraParams  width="+width+"  height="+height);
            Camera.Parameters parameters = mCamera.getParameters();
            // 获取摄像头支持的PictureSize列表
            List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
    //        for (Camera.Size size : pictureSizeList) {
    //            Log.i(TAG, "pictureSizeList size.width=" + size.width + "  size.height=" + size.height);
    //        }
            /**从列表中选取合适的分辨率*/
            Camera.Size picSize = getProperSize(pictureSizeList, ((float) height / width));
            if (null == picSize) {
                Log.i(TAG, "null == picSize");
                picSize = parameters.getPictureSize();
            }
            Log.i(TAG, "picSize.width=" + picSize.width + "  picSize.height=" + picSize.height);
             // 根据选出的PictureSize重新设置SurfaceView大小
            float w = picSize.width;
            float h = picSize.height;
            parameters.setPictureSize(picSize.width,picSize.height);
            this.setLayoutParams(new RelativeLayout.LayoutParams((int) (height*(h/w)), height));
    
            // 获取摄像头支持的PreviewSize列表
            List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
            Camera.Size preSize = getProperSize(previewSizeList, ((float) height) / width);
            if (null != preSize) {
                Log.i(TAG, "preSize.width=" + preSize.width + "  preSize.height=" + preSize.height);
                parameters.setPreviewSize(preSize.width, preSize.height);
            }
    
            parameters.setJpegQuality(100); // 设置照片质量
            if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式
            }
    
            parameters.set("orientation", "portrait");
            mCamera.cancelAutoFocus();//自动对焦。
            mCamera.setDisplayOrientation(90);// 设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示
            mCamera.setParameters(parameters);
    
        }
    
        /**
         * 从列表中选取合适的分辨率
         * 默认w:h = 4:3
         * <p>注意:这里的w对应屏幕的height
         *            h对应屏幕的width<p/>
         */
        private Camera.Size getProperSize(List<Camera.Size> pictureSizeList, float screenRatio) {
            Log.i(TAG, "screenRatio=" + screenRatio);
            Camera.Size result = null;
            for (Camera.Size size : pictureSizeList) {
                float currentRatio = ((float) size.width) / size.height;
                if (currentRatio - screenRatio == 0) {
                    result = size;
                    break;
                }
            }
    
            if (null == result) {
                for (Camera.Size size : pictureSizeList) {
                    float curRatio = ((float) size.width) / size.height;
                    if (curRatio == 4f / 3) {// 默认w:h = 4:3
                        result = size;
                        break;
                    }
                }
            }
    
            return result;
        }
    

    在完成预览设置之后可以根据需求进行拍照或者拍摄的操作:
    --首先时拍照操作:
    拍照时我们需要设置三个回调函数

        // 拍照瞬间调用
        private Camera.ShutterCallback shutter = new Camera.ShutterCallback() {
            @Override
            public void onShutter() {
    
            }
        };
    
        // 获得没有压缩过的图片数据
        private Camera.PictureCallback raw = new Camera.PictureCallback() {
    
            @Override
            public void onPictureTaken(byte[] data, Camera Camera) {
    
            }
        };
    
        //图像数据处理完成后的回调函数
        private Camera.PictureCallback jpeg = new Camera.PictureCallback() {
    
            @Override
            public void onPictureTaken(byte[] data, Camera Camera) {
                BufferedOutputStream bos = null;
                Bitmap bm = null;
                try {
                    // 获得图片
                    bm = BitmapFactory.decodeByteArray(data, 0, data.length);
                    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        Log.i(TAG, "Environment.getExternalStorageDirectory()="+Environment.getExternalStorageDirectory());
                        File file = new File(Environment.getExternalStorageDirectory()
                                + File.separator + "ysb/image/"+System.currentTimeMillis()+".jpg");//拍摄照片的保存地址
    //                    String filePath = "/sdcard/dyk"+System.currentTimeMillis()+".jpg";//照片保存路径
    //                    File file = new File(filePath);
                        if (!file.exists()){
                            file.mkdirs();
                        }
                        bos = new BufferedOutputStream(new FileOutputStream(file));
                        bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩到流中
    
                    }else{
                        Toast.makeText(getContext(),"没有检测到内存卡", Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        bos.flush();//输出
                        bos.close();//关闭
                        bm.recycle();// 回收bitmap空间
                        mCamera.stopPreview();// 关闭预览
                        mCamera.startPreview();// 开启预览
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
    
            }
        };
    

    然后调用camera.takePicture方法实现拍照

        /**
         * 拍照方法
         * @param onRecordFinishListener  拍照完成后的回调接口
         */
        public void takePicture(OnRecordFinishListener onRecordFinishListener) {
            this.mOnRecordFinishListener = onRecordFinishListener;
            pause();
                try {
                    if (!isOpenCamera)
                    initCamera();
                    Log.e(TAG, ""+mCamera);
                    this.mCamera.autoFocus(new AutoFocusCallback() {
                        @Override
                        public void onAutoFocus(boolean flag, Camera camera) {
                            camera.takePicture(shutter, raw, jpeg);
                        }
                    });
    //              mCamera.takePicture(null, null, jpeg);
                    if (mOnRecordFinishListener != null) 
                        mOnRecordFinishListener.onRecordFinish();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            
            
        }
    

    --或者进行拍摄操作
    在拍照之前我们需要先初始化MediaRecorder:

        /**
         * 初始化
         * @throws IOException
         */
        @SuppressLint("NewApi")
        private void initRecord() throws IOException {
            mMediaRecorder = new MediaRecorder();
            mMediaRecorder.reset();
            if (mCamera != null)
                mMediaRecorder.setCamera(mCamera);
            mMediaRecorder.setOnErrorListener(this);
            mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
            mMediaRecorder.setVideoSource(VideoSource.CAMERA);// 视频源
            mMediaRecorder.setAudioSource(AudioSource.MIC);// 音频源
            mMediaRecorder.setOutputFormat(OutputFormat.MPEG_4);// 视频输出格式
            mMediaRecorder.setAudioEncoder(AudioEncoder.AMR_NB);// 音频格式
            mMediaRecorder.setVideoSize(mWidth, mHeight);// 设置分辨率:
            // mMediaRecorder.setVideoFrameRate(16);// 这个我把它去掉了,感觉没什么用
            mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);// 设置帧频率,然后就清晰了
            mMediaRecorder.setOrientationHint(90);// 输出旋转90度,保持竖屏录制
            mMediaRecorder.setVideoEncoder(VideoEncoder.MPEG_4_SP);// 视频录制格式
            // mediaRecorder.setMaxDuration(Constant.MAXVEDIOTIME * 1000);
            mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath());
            mMediaRecorder.prepare();
            try {
                mMediaRecorder.start();//开始录制
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (RuntimeException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    这里的录制是限制了事件的长按录制

        /**
         * 开始录制视频
         * @param fileName
         *            视频储存位置
         * @param onRecordFinishListener
         *            达到指定时间之后回调接口
         */
        public void record(final OnRecordFinishListener onRecordFinishListener) {
            this.mOnRecordFinishListener = onRecordFinishListener;
            createRecordDir();
            try {
                if (!isOpenCamera)// 如果未打开摄像头,则打开
                    initCamera();
                mTimeCount = 0;// 时间计数器重新赋值
                initRecord();
                mTimer = new Timer();
                mTimer.schedule(new TimerTask() {
    
                    @Override
                    public void run() {
                        mTimeCount++;
                            mProgressBar.setProgress(mTimeCount);// 设置进度条
                            if ((mTimeCount-1) == mRecordMaxTime) {// 达到指定时间,停止拍摄
                                stop();
                                if (mOnRecordFinishListener != null)
                                    mOnRecordFinishListener.onRecordFinish();
                            }
                        }
                        
    //              }
                }, 0, 1000);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    在拍摄完成后要记住关闭摄像头释放资源

        /**
         * 停止拍摄
         */
        public void stop() {
            stopRecord();
            releaseRecord();
            freeCameraResource();
        }
    
        /**
         * 停止录制
         */
        public void stopRecord() {
            mProgressBar.setProgress(0);
            if (mTimer != null)
                mTimer.cancel();
            if (mMediaRecorder != null) {
                // 设置后不会崩
                mMediaRecorder.setOnErrorListener(null);
                mMediaRecorder.setPreviewDisplay(null);
                try {
                    mMediaRecorder.stop();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                } catch (RuntimeException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 释放资源
         */
        private void releaseRecord() {
            if (mMediaRecorder != null) {
                mMediaRecorder.setOnErrorListener(null);
                try {
                    mMediaRecorder.release();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            mMediaRecorder = null;
        }
    
        /**
         * 释放摄像头资源
         */
        private void freeCameraResource() {
            if (mCamera != null) {
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview();
    //          mCamera.lock();
                mCamera.release();
                mCamera = null;
                isOpenCamera = false;
            }
        }
    

    最后要记得添加权限

    <uses-permission android:name="android.permission.CAMERA" /><!-- 调用相机权限 -->
    <uses-feature android:name="android.hardware.camera.autofocus" /><!-- 自动聚焦权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- SD卡写权限 -->
    

    总结
    Android自定义相机实现的主要问题在于实现预览时图像会旋转不同的方向,我们需要根据不同的手机方向设置预览图像旋转角度。
    这里因为设置不可旋转,所以只需要设置

    mCamera.setDisplayOrientation(90);
    

    相关文章

      网友评论

      • 96ab13fc0224:可以用 赞
      • 0d8f1dbba6c8:还有就是你录制的时候不变形!录制完成后会不会变形,就是预览的时候不变形。我预览的时候是全屏。视频输出的时候改变了宽高就变形了
      • 0d8f1dbba6c8:你好。可以发我一份demo吗?我现在也在做自定义视频录制。可是变形。

      本文标题:Android 实现自定义相机

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