Androidカメラの原理解析(ソースコード)

39212 ワード

アプリケーション開発において、画像データは、1つの会社にとって非常に重要であり、例えば、画像資料をアップロードしたり、ユーザーの顔を修正したりするなど、カメラやアルバムの使用から離れられない.iosプラットフォームでは、システムカメラやアルバムを直接呼び出すと、すべてを解決できます.しかし、Androidプラットフォームにとって、システムカメラやアルバムを直接呼び出すのは、適合や体験に問題が多い.具体的な原因は、Androidブランドが多すぎて雑で、性能が異なることも知られている.これに鑑みて、開発の過程で、同様の問題に遭遇した場合、カメラやアルバムの機能を実現して、体験が完全であることを保証することをお勧めします.このブログでは、カメラの実現に重点を置きます.
まず、2つのgithubプロジェクトをお勧めします.直接使えるカメラとアルバムです.また、連絡先セレクタも推奨されます.
カメラ:CameraDemo(カスタムカメラ)
アルバム:ImageSelector
連絡先:ContactSelector(連絡先セレクタ)
一、Cameraを開く
        try {
                mCamera = Camera.open();//    
            } catch (RuntimeException e) {
                e.printStackTrace();
                LogUtil.d(TAG, "     ,            ");
                ToastUtil.getInstance().toast("     ,            ");
                return;
            }

二、Cameraパラメータの設定
デフォルトサイズは自由に設定できますが、ここでは携帯電話の解像度をデフォルトサイズにします.
1.指定された解像度に基づいてカメラの最適な解像度を検索し、設定する
    private void setCameraParams(int width, int height) {
        LogUtil.i(TAG, "setCameraParams  width=" + width + "  height=" + height);

        Camera.Parameters parameters = mCamera.getParameters();

        List pictureSizeList = parameters.getSupportedPictureSizes();
        sort(pictureSizeList);//  
        for (Camera.Size size : pictureSizeList) {
            LogUtil.i(TAG, "         :" + " size.width=" + size.width + "  size.height=" + size.height);
        }
        Camera.Size picSize = getBestSupportedSize(pictureSizeList, ((float) height / width));//            
        if (null == picSize) {
            picSize = parameters.getPictureSize();
        }

        LogUtil.e(TAG, "           :" + "picSize.width=" + picSize.width + "  picSize.height=" + picSize.height);
        //      PictureSize    SurfaceView  
        parameters.setPictureSize(picSize.width, picSize.height);
        ....

2.指定された解像度に基づいてカメラの最適なプレビュー解像度を検索し、設定する
    private void setCameraParams(int width, int height) {
         LogUtil.i(TAG, "setCameraParams  width=" + width + "  height=" + height);

        Camera.Parameters parameters = mCamera.getParameters();

        /***************************         PreviewSize  ********************/
        List previewSizeList = parameters.getSupportedPreviewSizes();
        sort(previewSizeList);
        for (Camera.Size size : previewSizeList) {
            LogUtil.i(TAG, "           :" + " size.width=" + size.width + "  size.height=" + size.height);
        }
        Camera.Size preSize = getBestSupportedSize(previewSizeList, ((float) height) / width);
        if (null != preSize) {
            LogUtil.e(TAG, "          :" + "preSize.width=" + preSize.width + "  preSize.height=" + preSize.height);
            parameters.setPreviewSize(preSize.width, preSize.height);
        }
       ......

3.最適解像度適合アルゴリズム(先ソート)
    /**
     *        ,      ,   ,       
     *   :      ,1.            ->2.            ->3.       
     *
     * @param sizes     
     * @return        
     */
    private Camera.Size getBestSupportedSize(List sizes, float screenRatio) {
        Camera.Size largestSize = null;
        int largestArea = 0;
        for (Camera.Size size : sizes) {
            if ((float) size.height / (float) size.width == screenRatio) {
                if (size.width == DEFAULT_PHOTO_WIDTH && size.height == DEFAULT_PHOTO_HEIGHT) {
                    //        ,      
                    largestSize = size;
                    break;
                } else if (size.height == DEFAULT_PHOTO_HEIGHT || size.width == DEFAULT_PHOTO_WIDTH) {
                    largestSize = size;
                    break;
                }
                int area = size.height + size.width;
                if (area > largestArea) {//         
                    largestArea = area;
                    largestSize = size;
                }
            } else if (size.height == DEFAULT_PHOTO_HEIGHT || size.width == DEFAULT_PHOTO_WIDTH) {
                largestSize = size;
                break;
            }
        }
        if (largestSize == null) {
            largestSize = sizes.get(sizes.size() - 1);
        }
        return largestSize;
    }

4.フォーカスモード一部のスマートフォンのため、フロントカメラはフォーカスモードがなく、フォーカスパラメータの設定はフロントカメラを区別すべきである
   //        
        if(cameraId == Camera.CameraInfo.CAMERA_FACING_BACK){
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//        
        }

5.画像品質PixelFormatには複数のモードがあり、ソースコードには解がある.
        //    
        parameters.setJpegQuality(100); //       
        parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP); //     
        parameters.setPictureFormat(PixelFormat.JPEG); //      JPEG,   NV21

6.点滅灯及び縦画面レンズ調整
        //     
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);

        //          
        if (mContext.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }

7.カメラ異常傍受
        //      
        mCamera.setErrorCallback(new Camera.ErrorCallback() {

            @Override
            public void onError(int error, Camera camera) {
                String error_str;
                switch (error) {
                    case Camera.CAMERA_ERROR_SERVER_DIED: //       
                        error_str = "      ";
                        break;

                    case Camera.CAMERA_ERROR_UNKNOWN:
                        error_str = "     ,            ";
                        break;

                    default:
                        error_str = "     ,            ";
                        break;
                }
                ToastUtil.getInstance().toast(error_str);
                Log.i(TAG, error_str);
            }
        });

完全なパラメータ設定コード:
  /**
     *         
     *
     * @param width   
     * @param height  
     */
    private void setCameraParams(int width, int height) {
        LogUtil.i(TAG, "setCameraParams  width=" + width + "  height=" + height);

        Camera.Parameters parameters = mCamera.getParameters();


        /***************************         PictureSize  ********************/
        List pictureSizeList = parameters.getSupportedPictureSizes();
        sort(pictureSizeList);//  
        for (Camera.Size size : pictureSizeList) {
            LogUtil.i(TAG, "         :" + " size.width=" + size.width + "  size.height=" + size.height);
        }
        Camera.Size picSize = getBestSupportedSize(pictureSizeList, ((float) height / width));//            
        if (null == picSize) {
            picSize = parameters.getPictureSize();
        }

        LogUtil.e(TAG, "           :" + "picSize.width=" + picSize.width + "  picSize.height=" + picSize.height);
        //      PictureSize    SurfaceView  
        parameters.setPictureSize(picSize.width, picSize.height);


        /***************************         PreviewSize  ********************/
        List previewSizeList = parameters.getSupportedPreviewSizes();
        sort(previewSizeList);
        for (Camera.Size size : previewSizeList) {
            LogUtil.i(TAG, "           :" + " size.width=" + size.width + "  size.height=" + size.height);
        }
        Camera.Size preSize = getBestSupportedSize(previewSizeList, ((float) height) / width);
        if (null != preSize) {
            LogUtil.e(TAG, "          :" + "preSize.width=" + preSize.width + "  preSize.height=" + preSize.height);
            parameters.setPreviewSize(preSize.width, preSize.height);
        }

        /***************************         ********************/
        if(cameraId == Camera.CameraInfo.CAMERA_FACING_BACK){
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//        
        }
        //    
        parameters.setJpegQuality(100); //       
        parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP); //     
        parameters.setPictureFormat(PixelFormat.JPEG); //      JPEG,   NV21

        //     
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);

        //          
        if (mContext.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }

        //      
        mCamera.setErrorCallback(new Camera.ErrorCallback() {

            @Override
            public void onError(int error, Camera camera) {
                String error_str;
                switch (error) {
                    case Camera.CAMERA_ERROR_SERVER_DIED: //       
                        error_str = "      ";
                        break;

                    case Camera.CAMERA_ERROR_UNKNOWN:
                        error_str = "     ,            ";
                        break;

                    default:
                        error_str = "     ,            ";
                        break;
                }
                ToastUtil.getInstance().toast(error_str);
                Log.i(TAG, error_str);
            }
        });
        mCamera.cancelAutoFocus();
        mCamera.setParameters(parameters);
    }

三、ピント合わせ
クリックフォーカスを実現するには、フォーカスリングを実現するには、フォーカスリングViewをカスタマイズする必要がある.
1.カスタムフォーカスリングView-CameraFocusView
コア機能は、フォーカスリングが縮小し、緑になることです.アニメーションでフォーカスリングの半径を変更すればいいです.
     @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(isShow){
            if(radius == GREEN_RADIUS){
                mPaint.setColor(Color.GREEN);
            }
            if(centerPoint!=null){
                canvas.drawCircle(centerPoint.x, centerPoint.y, radius, mPaint);
            }
        }
    }

    private void showAnimView() {
        isShow = true;
        if (lineAnimator == null) {
            lineAnimator = ValueAnimator.ofInt(0, 20);
            lineAnimator.setDuration(DURATION_TIME);
            lineAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int animationValue = (Integer) animation
                            .getAnimatedValue();
                    if(lastValue!=animationValue&&radius>=(int) ((mScreenWidth * 0.1)-20)){
                        radius = radius - animationValue;
                        lastValue = animationValue;
                    }
                    isShow = true;
                    invalidate();
                }
            });
            lineAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    isShow = false;
                    lastValue = 0;
                    mPaint.setColor(Color.WHITE);
                    radius = (int) (mScreenWidth * 0.1);
                    invalidate();
                }
            });
        }else{
            lineAnimator.end();
            lineAnimator.cancel();
            lineAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            lineAnimator.start();
        }
    }

2.レイアウト画面
フォーカスリングのカスタムViewにインタフェース全体のタッチイベントを取得させる
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.awen.camera.widget.CameraSurfaceView
        android:id="@+id/cameraSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.awen.camera.widget.CameraFocusView
        android:id="@+id/cameraFocusView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    ......

3.フォーカスインタフェースの定義
i.インタフェースの定義
    /**
     *        
     */
    public interface IAutoFocus {
        void autoFocus(float x,float y);
    }

ii.フォーカスリングViewタッチイベントのトリガインタフェース:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                int x = (int) event.getX();
                int y = (int) event.getY();
                lastValue = 0;
                mPaint.setColor(Color.WHITE);
                radius = (int) (mScreenWidth * 0.1);
                centerPoint = null;
                if(y>TOP_CONTROL_HEIGHT&&y//              (      )
                    centerPoint = new Point(x, y);
                    showAnimView();
                    //    
                    if (mIAutoFocus != null) {
                        mIAutoFocus.autoFocus(event.getX(),event.getY());
                    }
                }
                break;
        }
        return true;
    }

4.CameraSurfaceViewでフォーカスを実現
i.合焦領域の算出
   private Rect caculateFocusPoint(int x, int y) {
        Rect rect = new Rect(x - 100, y - 100, x + 100, y + 100);
        int left = rect.left * 2000 / getWidth() - 1000;
        int top = rect.top * 2000 / getHeight() - 1000;
        int right = rect.right * 2000 / getWidth() - 1000;
        int bottom = rect.bottom * 2000 / getHeight() - 1000;
        //      (-1000,1000) (1000, 1000)   ,        
        left = left < -1000 ? -1000 : left;
        top = top < -1000 ? -1000 : top;
        right = right > 1000 ? 1000 : right;
        bottom = bottom > 1000 ? 1000 : bottom;
        return new Rect(left, top, right, bottom);
    }

ii.パラメータを設定してフォーカス
      private void camerFocus(Rect rect) {
        if (mCamera != null) {
            Camera.Parameters parameters = mCamera.getParameters();
            if(cameraId == Camera.CameraInfo.CAMERA_FACING_BACK){
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//        
            }
            if (parameters.getMaxNumFocusAreas() > 0) {
                List focusAreas = new ArrayList();
                focusAreas.add(new Camera.Area(rect, 1000));
                parameters.setFocusAreas(focusAreas);
            }
            mCamera.cancelAutoFocus(); //                
            mCamera.setParameters(parameters);
            mCamera.autoFocus(this);
        }

四、写真を撮る
画像のプレビューを容易にするためには、画像を処理する必要があるので、カメラの撮影時の方向を知る必要があるので、写真を撮るにはまず写真の方向パラメータを設定する必要があります.
1.Camera方向リスナー
/**
 *        ,          
 * Created by AwenZeng on 2017/2/21.
 */

public class CameraOrientationDetector extends OrientationEventListener {
    int mOrientation;

    public CameraOrientationDetector(Context context, int rate) {
        super(context, rate);
    }

    @Override
    public void onOrientationChanged(int orientation) {
        this.mOrientation = orientation;
        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
            return;
        }
        //         ,   0°、90°、180° 270°    
        int newOrientation = ((orientation + 45) / 90 * 90) % 360;
        if (newOrientation != mOrientation) {
            mOrientation = newOrientation;
        }
    }

    public int getOrientation() {
        return mOrientation;
    }
}

2.写真方向パラメータの設定
    /**
     *   
     *
     * @param callback
     */
    public void takePicture(Camera.PictureCallback callback) {
        if (mCamera != null) {
            int orientation = mCameraOrientation.getOrientation();
            Camera.Parameters cameraParameter = mCamera.getParameters();
            if (orientation == 90) {
                cameraParameter.setRotation(90);
                cameraParameter.set("rotation", 90);
            } else if (orientation == 180) {
                cameraParameter.setRotation(180);
                cameraParameter.set("rotation", 180);
            } else if (orientation == 270) {
                cameraParameter.setRotation(270);
                cameraParameter.set("rotation", 270);
            } else {
                cameraParameter.setRotation(0);
                cameraParameter.set("rotation", 0);
            }
            mCamera.setParameters(cameraParameter);
        }
        mCamera.takePicture(null, null, callback);
    }

3.画像の保存
プレビューを容易にするためには,異なる方向の画像に対して順方向の処理が必要である.
  public String handlePhoto(byte[] data, int cameraId) {
        String filePath = FileUtil.saveFile(data, "/DCIM");
        if (!TextUtils.isEmpty(filePath)) {
            int degree = BitmapUtil.getPhotoDegree(filePath);
            Log.i(TAG, degree + "");
            Bitmap bitmap = BitmapFactory.decodeFile(filePath);
            Bitmap tBitmap = null;
            try {
                Log.i(TAG, "      :"+"width = " + bitmap.getWidth() + "   ------ height = " + bitmap.getHeight());
                if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    switch (degree) {
                        case 0:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 90);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                        case 90:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 180);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                        case 180:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 270);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                        case 270:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 360);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                    }
                } else if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    switch (degree) {
                        case 0:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 270);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                        case 90:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 180);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                        case 180:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 90);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                        case 270:
                            tBitmap = BitmapUtil.rotateBitmap(bitmap, 360);
                            filePath = BitmapUtil.saveBitmap(tBitmap == null ? bitmap : tBitmap, filePath);
                            break;
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
                //     
                return "";
            } finally {
                if (bitmap != null) {
                    bitmap.recycle();
                    bitmap = null;
                }
                if (tBitmap != null) {
                    tBitmap.recycle();
                    tBitmap = null;
                }
                ScannerByReceiver(mContext, filePath);//    
            }
            return filePath;
        }
        return null;
    }

五、カメラの切り替え
   /**
     *      
     */
    public void changeCamera(int camera_id) {
        mCamera.stopPreview();
        mCamera.release();
        try {
            openCamera(camera_id);
            mCamera.setPreviewDisplay(holder);
            setCameraParams(DEFAULT_PHOTO_WIDTH, DEFAULT_PHOTO_HEIGHT);
            mCamera.startPreview();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public boolean openCamera(int camera_id) {
        LogUtil.i(TAG, "openCamera id = " + camera_id);
        try {
            mCamera = Camera.open(camera_id); //      
            cameraId = camera_id;

        } catch (Exception e) {
            e.printStackTrace();
            ToastUtil.getInstance().toast("         ");
            LogUtil.i(TAG, "         ");
            return false;
        }

        return true;
    }

六、フラッシュをオンまたはオフにする
    /**
     *      
     *
     * @param isOpen
     */
    public void changeFlashMode(boolean isOpen, Camera mCamera, int cameraId) {
        if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { //          
            Camera.Parameters parameters = mCamera.getParameters();
            PackageManager pm = mContext.getPackageManager();
            FeatureInfo[] features = pm.getSystemAvailableFeatures();
            for (FeatureInfo f : features) {
                if (PackageManager.FEATURE_CAMERA_FLASH.equals(f.name)) { //            
                    if (isOpen) {
                        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); //     

                    } else {
                        parameters
                                .setFlashMode(Camera.Parameters.FLASH_MODE_OFF); //     

                    }
                }
            }
            mCamera.setParameters(parameters);
        }
    }

注意事項
  • Android6.0以上の権限が締め付けられているので、カメラを使用する前に、PermissionsModelで権限判断をしてください.具体的なAndroid 6.0アクセス権
  • 一部のスマートフォン、フロントカメラのフォーカスモードがなく、フォーカスパラメータの設定はフロントカメラ
  • を区別しなければならない.
  • Android5.0以降は、公式推奨ではCamera 2が使用されているが、この例では新バージョンは使用されていない.