Android 自定義Camera + TextureView拍照

Android 自定義Camera + TextureView拍照#

自定義camera需要注意這幾點:

  1. camera預覽的角度。
  2. textureview的寬高比和camera預覽時設置的寬高比。
  3. 拍照之後圖片的旋轉角度。

在自定義相機之前可以看下這篇文章,瞭解一下相機傳感器的方向問題https://blog.csdn.net/c10WTiybQ1Ye3/article/details/78098459

在解決詳解預覽的角度問題,官方有一個推薦的寫法。

  /**
 * 保證預覽方向正確
 *
 * @param context
 * @param cameraId
 * @param camera
 */
public void setCameraDisplayOrientation(Activity context,
                                        int cameraId, Camera camera) {
    android.hardware.Camera.CameraInfo info =
            new android.hardware.Camera.CameraInfo();
    android.hardware.Camera.getCameraInfo(cameraId, info);
    int rotation = context.getWindowManager().getDefaultDisplay()
            .getRotation();
    int degrees = 0;
    switch (rotation) {
        case Surface.ROTATION_0: degrees = 0; break;
        case Surface.ROTATION_90: degrees = 90; break;
        case Surface.ROTATION_180: degrees = 180; break;
        case Surface.ROTATION_270: degrees = 270; break;
    }

    int result;
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        result = (info.orientation + degrees) % 360;
        result = (360 - result) % 360;  // compensate the mirror
    } else {  // back-facing
        result = (info.orientation - degrees + 360) % 360;
    }

    camera.setDisplayOrientation(result);
}

在預覽是如果寬高設置的不恰當不僅可能會出現崩潰的情況,還會出現預覽界面變形的情況,爲了解決變形的情況,我們應該讓預覽界面的寬高比例和textureview的寬高比例儘量達到一致,同時在解決因爲寬高設置的不恰當問題,我們也應該在相機支持的分辨率中去尋找。我爲了更方便的控制寬高的比例,我設置的textureview的寬高是充滿屏幕的。那麼屏幕的寬高比例,就是相機分辨率的寬高比例。注意:因爲相機傳感器是橫屏安裝的,所以應該用屏幕的h/w,來確定設置的分辨率的w/h

 /**
 *
 *
 * @param sizes 相機支持的size
 * @param targetRatio h/w
 * @param comparator 升序或者降序
 * @param minWidth 最小的支持寬度
 * @return
 */
public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, float targetRatio, int comparator, int minWidth) {
    if (sizes == null)
        return null;
    Camera.Size optimalSize = null;


	// 對size進行排序
    Collections.sort(sizes, getComparator(comparator));

    //先找出寬度大於最小寬度的size
    List<Camera.Size> tempList = new ArrayList<>();
    for(Camera.Size size : sizes){
        Log.i("sss", "....width.....:"+size.width+"...height.."+size.height);
        if(size.width >= minWidth){
            tempList.add(size);
        }
    }

    if(tempList.size() > 0){
        // 找比例相同的,這裏是整個屏幕的高和寬的比。
        for (Camera.Size size : tempList) {
            float currentRatio = ((float) size.width) / size.height;
            if (currentRatio - targetRatio == 0) {
                optimalSize = size;
                break;
            }
        }
    }else{
        //比例相同的
        for (Camera.Size size : sizes) {
            float currentRatio = ((float) size.width) / size.height;
            if (currentRatio - targetRatio == 0) {
                optimalSize = size;
                break;
            }
        }
    }

    // 如果沒有就找個相近的
    if(optimalSize == null){
        float tempRation;
        float minRation = Float.MAX_VALUE;
        if(tempList.size() > 0){
            for (Camera.Size size : tempList) {
                float curRatio = ((float) size.width) / size.height;
                tempRation = Math.abs(targetRatio - curRatio);
                if(tempRation <minRation){
                    minRation = tempRation;
                    optimalSize = size;
                }
            }
        }else{
            for (Camera.Size size : sizes) {
                float curRatio = ((float) size.width) / size.height;
                tempRation = Math.abs(targetRatio - curRatio);
                if(tempRation <minRation){
                    minRation = tempRation;
                    optimalSize = size;
                }
            }
        }
    }

    Log.i("sss", "最後的選擇是:"+optimalSize.width+"....."+optimalSize.height);
    return optimalSize;
}

拍照之後圖片旋轉角度,對於圖片的處理可以參考這片文章https://blog.csdn.net/Lamphogani/article/details/79197015?utm_source=blogxgwz9
我想要的要效果是圖片的方向跟着手機的方向來設定,因此我使用的是

  orientationEventListener = new OrientationEventListener(context) {
        @Override
        public void onOrientationChanged(int orientation) {
            if (ORIENTATION_UNKNOWN == orientation) {
                return;
            }
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId, info);

            orientation = (orientation + 45) / 90 * 90;
            rotation = 0;
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                rotation = (info.orientation - orientation + 360) % 360;
            } else {
                rotation = (info.orientation + orientation) % 360;
            }
			// 我不知道怎麼回事,我的這個設置並沒起到在拍照之後圖片旋轉的效果
            if (null != mCamera) {
                Camera.Parameters parameters = mCamera.getParameters();
                parameters.setRotation(rotation);
                mCamera.setParameters(parameters);
            }
        }
    };

我只是在這記錄拍照是的手機的角度,之後對圖片進行的旋轉

 /**
 * 把相機拍照返回照片轉正
 *
 * @param angle 旋轉角度
 * @return bitmap 圖片
 */
public  Bitmap rotaingImageView(int id, int angle, Bitmap bitmap) {
    //旋轉圖片 動作
    Matrix matrix = new Matrix();
    matrix.postRotate(angle);
    //加入翻轉 把相機拍照返回照片轉正
    if (id == 1) {
        matrix.postScale(-1, 1);
    }
    // 創建新的圖片, 如果傳入的角度是bitmap當前的角度的話,
    // 就不會做重新生成一個新的bitmap。只是把bitmap的值給了resizeBitmap
    Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
            bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    if(resizedBitmap == null){
        resizedBitmap = bitmap;
    }
  
    return resizedBitmap;
}

注意到以上幾個問題就差不多了,剩下的就是具體的自定義了。

首先做簡單的設置,因爲我使用的是camera+textureview,所以應該開啓窗口加速,我在setcontentview中設置了

  //此行代碼必須存在,是TextureView必要在窗口加速中才能使用
    getWindow().setFlags(
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

    requestWindowFeature(Window.FEATURE_NO_TITLE); //設置無標題

    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);  //設置全屏
    this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//拍照過程屏幕一直處於高亮
    //設置手機屏幕朝向,一共有7種
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

然後就是權限的申請,同時也註冊了textureView.setSurfaceTextureListener(this);在權限申請通過和textureview創建成功之後,就可以初始化camera同時開啓camera的預覽了

`  // 權限
String[] PERMISSIONS = {Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO
};`

初始化camera

`/**
 *
 * @param surfaceTexture
 * @param cameraId
 */
@Override
public void startPreview(SurfaceTexture surfaceTexture, int cameraId) {

    this.cameraId = cameraId;
    this.mSurfaceTexture = surfaceTexture;

    if (mCamera == null && mSurfaceTexture != null) {

        if(orientationEventListener != null){
            orientationEventListener.enable();
        }

        // 獲取camera實例對象
        mCamera = getCameraInstance(cameraId);
        // 開啓預覽
        try {
            if (mCamera != null) {
                mCamera.setPreviewTexture(mSurfaceTexture);
                mCamera.lock();
                setCameraDisplayOrientation(context, cameraId, mCamera);

                initCameraParameters();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


/** 安全獲取Camera對象實例的方法 */
private Camera getCameraInstance(int cameraId) {
    try {
        mCamera = Camera.open(cameraId); // 試圖獲取Camera實例
    }
    catch (Exception e) {
        // 攝像頭不可用(正被佔用或不存在)
    }
    return mCamera; // 不可用則返回null
}


` /**
 * 初始化攝像頭參數
 */
private void initCameraParameters() {
    // 初始化攝像頭參數
    mParameters = mCamera.getParameters();
    mCamera.lock();

    List<String> focusModes = mParameters.getSupportedFocusModes();
    // 設置對焦模式
    if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
        // Autofocus mode is supported 自動對焦
        mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
    }
    if(focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)){
        mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 1連續對焦
    }
//         預覽尺寸
    previewSize = getOptimalPreviewSize(mParameters.getSupportedPreviewSizes(), DisplayUtils.getScreenRate(context), Constants.COMPARATOR_ASCEND, 1280);
    if (previewSize != null) {
        mParameters.setPreviewSize(previewSize.width, previewSize.height);
    }
    // 圖片尺寸
    Camera.Size pictrueSize = getOptimalPreviewSize(mParameters.getSupportedPictureSizes(), DisplayUtils.getScreenRate(context), Constants.COMPARATOR_ASCEND, 1280);
    if (pictrueSize != null) {
        mParameters.setPictureSize(pictrueSize.width, pictrueSize.height);
    }
    //設置預覽格式
    mParameters.setPreviewFormat(ImageFormat.NV21);

    try {
        mCamera.setParameters(mParameters);
        mCamera.setPreviewCallback(mRecordingUtils);
        mCamera.startPreview();
        // 2如果要實現連續的自動對焦,這一句必須加上
        mCamera.cancelAutoFocus();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

`

最後就是camera的拍照操作了。。。拍照有兩個方法,一個是從camera.setPreviewCallback的監聽中,獲取拍照時的流,然後生成圖片,另一種就是通過camera的takePicture方法獲取。我使用的是第二種。雖然後面也的使用第一種監聽中的方法進行視屏的錄製。

mCamera.takePicture(null, null, new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(final byte[] data, Camera camera) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
					//進行了圖片的旋轉,因爲是耗時所以我放在了子線程中去做
                    Bitmap savebitmap = rotaingImageView(cameraId, tempRotation, bitmap);

                    String img_path = imagePath;
                    if(TextUtils.isEmpty(img_path)){
                        img_path = Constants.DEFAULT_DIRECTORY;
                    }

                    img_path = img_path + "/" + System.currentTimeMillis() + ".jpeg";
                    File file = BitmapUtils.saveJPGE_After(context, savebitmap, img_path, 100);

                    if(cameraResultCallBack != null){
                        cameraResultCallBack.takePhotoResult(file);
                    }

					if(bitmap != null && !bitmap.isRecycled()){
					    bitmap.recycle();
					}

                    if(savebitmap != null && !savebitmap.isRecycled()){
                        savebitmap.recycle();
                        savebitmap = null;
                    }

                }
            }).start();

            mCamera.startPreview();
        }
    });
``

以上就是自定義camera的基本操作流程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章