Android 用 camera2 API 自定義相機

ThelmaMahur 7年前發布 | 18K 次閱讀 API 安卓開發 Android開發 移動開發

前言

筆者因為項目需要自定義相機,所以了解了一下 Android 關于 camera 這塊的 API。Android SDK 21(LOLLIPOP) 開始已經棄用了之前的 Camera 類,提供了 camera2 相關 API,目前網上關于 camera2 API 介紹的資料比較少,筆者搜集網上資料,結合自己的實踐,在這里做一個總結。

流程

因為 camera2 提供的接口比較多,雖然很靈活,但是也增加了使用的復雜度。首先來大致了解一下調用 camera2 的流程,方便我們理清思路。

要顯示相機捕捉的畫面,只需要三步:初始化相機,預覽,更新預覽。也就是上圖中左側的部分。要實現這三步,需要用到的主要接口類和它們的作用步驟如上圖右側部分所示。下面就用代碼來詳解一下。

案例

首先創建一個相機界面:

activity_camera.xml

<LinearLayout xmlns:android="

<TextureView
        android:id="@+id/camera_texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

<ImageButton
        android:id="@+id/capture_ib"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginBottom="10dp"
        android:layout_gravity="bottom|center"
        android:background="@drawable/send_pres"/>

</LinearLayout></code></pre>

界面很簡單,只有一個 TexureView 和一個按鈕。

接下來在 Activity 中初始化并顯示相機捕捉的畫面。

首先要解決的一個問題就是畫面拉伸的問題。

要解決這個問題,首先要從 TextureView 下手。

CameraActivity.java

mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
                mWidth = width;
                mHeight = height;
                getCameraId();
                openCamera();
            }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

        }
    });</code></pre> 

在 onSurfaceTextureAvailable 中初始化相機。通過 CameraManager 對象 openCamera,這正是流程圖中 Init 步驟中的第一步。openCamera 有三個參數,第一個是 String 類型的 cameraId,第二個是 CameraDevice.StateCallback,第三個是 Handler。這里我們要聲明一個 StateCallback:

private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            createCameraPreview();
        }

        @Override
        public void onDisconnected(CameraDevice cameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(CameraDevice cameraDevice, int i) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    };

可以看到,在 camera 準備完畢之后就可以創建預覽界面了。解決畫面拉伸的問題就是要為預覽界面設置一個合適比例的 SurfaceTexture buffer size。

private void createCameraPreview() {
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            int deviceOrientation = getWindowManager().getDefaultDisplay().getOrientation();
            int totalRotation = sensorToDeviceRotation(characteristics, deviceOrientation);
            boolean swapRotation = totalRotation == 90 || totalRotation == 270;
            int rotatedWidth = mWidth;
            int rotatedHeight = mHeight;
            if (swapRotation) {
                rotatedWidth = mHeight;
                rotatedHeight = mWidth;
            }
            mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth, rotatedHeight);
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            Log.e("CameraActivity", "OptimalSize width: " + mPreviewSize.getWidth() + " height: " + mPreviewSize.getHeight());
            ...

這里根據當前設備及傳感器的旋轉角度來判斷是否交換寬高值,然后通過 CameraCharacteristics 來得到最適合當前大小比例的寬高,然后把這個寬高設置給 SurfaceTexture 。

private Size getPreferredPreviewSize(Size[] sizes, int width, int height) {
        List<Size> collectorSizes = new ArrayList<>();
        for (Size option : sizes) {
            if (width > height) {
                if (option.getWidth() > width && option.getHeight() > height) {
                    collectorSizes.add(option);
                }
            } else {
                if (option.getHeight() > width && option.getWidth() > height) {
                    collectorSizes.add(option);
                }
            }
        }
        if (collectorSizes.size() > 0) {
            return Collections.min(collectorSizes, new Comparator<Size>() {
                @Override
                public int compare(Size s1, Size s2) {
                    return Long.signum(s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight());
                }
            });
        }
        return sizes[0];
    }

這里 Sizes 是相機返回的支持的分辨率,從我們傳遞的參數找找到一個最接近的分辨率。

接下來就要通過 CaptureRequest.Builder以及 CameraCaptureSession.StateCallback 來創建及更新預覽界面:

...
Surface surface = new Surface(texture);
            mBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 設置預覽對象
            mBuilder.addTarget(surface);
            mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                    if (null == mCameraDevice) {
                        return;
                    }
                    mSession = cameraCaptureSession;
                    mBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
                    try {
                        // 不停地將捕捉的畫面更新到 TextureView
                        mSession.setRepeatingRequest(mBuilder.build(), mSessionCaptureCallback, mBackgroundHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                    Toast.makeText(CameraActivity.this, "Camera configuration change", Toast.LENGTH_SHORT).show();
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

 

來自:http://www.cnblogs.com/jpush88/p/6670724.html

 

 本文由用戶 ThelmaMahur 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!