開始使用Android CameraX

安卓中使用相機從來就不是一件容易的事。

Camera1要自己管理Camera相機實例,要處理SufraceView相關的一堆東西,還有預覽尺寸跟畫面尺寸的選擇,頁面生命周期切換等等問題。。。

后來推出了Camera2,從官方Demo
就上千行代碼來看,Camera2并不解決用起來復雜的問題,它提供了更多的調用接口,可定制性更好,結果就是對普通開發(fā)者來說更難用了。。。

終于Google也意識到這個問題,推出了最終版CameraX. CameraX實際上還是用的Camera2,但它對調用API進行了很好的封裝,使用起來非常方便。官方教程也很詳細,如下:
https://codelabs.developers.google.com/codelabs/camerax-getting-started/#0

官方用的Kotlin代碼,我轉成了Java,其實用起來差不多。

注意:CameraX跟Camera2一樣最低支持API21,也就是5.0及以上。
開發(fā)環(huán)境用Android Studio3.3及以上,依賴庫都用androidx的

1 導入依賴

在app的build.gradle中加入


    implementation 'androidx.appcompat:appcompat:1.1.0-rc01'

    // Use the most recent version of CameraX, currently that is alpha04
    def camerax_version = "1.0.0-alpha04"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"

2 布局文件和權限

放一個TextureView就行了

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextureView
            android:id="@+id/view_finder"
            android:layout_width="640px"
            android:layout_height="640px"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

權在AndroidManifest.xml中加入相機權限

<uses-permission android:name="android.permission.CAMERA" />

并加入動態(tài)申請權限代碼,這里省略掉(你要在App安裝后手動打開相機權限)。

3 啟動相機

給TextureView設置布局變化的監(jiān)聽,用updateTransform()更新相機預覽,然后startCamera()啟動相機


        TextureView viewFinder = findViewById(R.id.view_finder);
        viewFinder.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
                updateTransform();
            }
        });

        viewFinder.post(new Runnable() {
            @Override
            public void run() {
                startCamera();
            }
        });

更新相機預覽:主要是給TextureView設置一個旋轉的矩陣變化,防止預覽方向不對

    private void updateTransform() {
        Matrix matrix = new Matrix();
        // Compute the center of the view finder
        float centerX = viewFinder.getWidth() / 2f;
        float centerY = viewFinder.getHeight() / 2f;

        float[] rotations = {0,90,180,270};
        // Correct preview output to account for display rotation
        float rotationDegrees = rotations[viewFinder.getDisplay().getRotation()];

        matrix.postRotate(-rotationDegrees, centerX, centerY);

        // Finally, apply transformations to our TextureView
        viewFinder.setTransform(matrix);
    }

啟動相機:創(chuàng)建PreviewConfig和Preview這兩個對象,可以設置預覽圖像的尺寸和比例,在OnPreviewOutputUpdateListener回調中用setSurfaceTexture方法,將相機圖像輸出到TextureView。最后用CameraX.bindToLifecycle方法將相機與當前頁面的生命周期綁定。

    private void startCamera() {
        // 1. preview
        PreviewConfig previewConfig = new PreviewConfig.Builder()
                .setTargetAspectRatio(new Rational(1, 1))
                .setTargetResolution(new Size(640,640))
                .build();

        Preview preview = new Preview(previewConfig);
        preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
            @Override
            public void onUpdated(Preview.PreviewOutput output) {
                ViewGroup parent = (ViewGroup) viewFinder.getParent();
                parent.removeView(viewFinder);
                parent.addView(viewFinder, 0);

                viewFinder.setSurfaceTexture(output.getSurfaceTexture());
                updateTransform();
            }
        });

        CameraX.bindToLifecycle(this, preview);

這樣就實現(xiàn)了基本的相機預覽功能。這幾個方法都很簡單明了,對外只依賴一個TextureView。生命周期自動綁定,這意味著代碼可以寫在一塊,在一處調用。不像以前這里插一段代碼,那里插一段代碼。

還有最大的好處,就是可擴展性。相機預覽使用了PreviewConfig和Preview兩個對象,加入新的相機功能同樣是加兩個對象XXXConfig和XXX,其他地方都不同改!

加入拍照功能就加入ImageCaptureConfig和ImageCapture,加入圖像分析功能就加入ImageAnalysisConfig和ImageAnalysis,非常方便統(tǒng)一。

4 拍照

創(chuàng)建ImageCaptureConfig和ImageCapture這兩個對象,用imageCapture.takePicture方法傳入相片保存地址就行了。當然在生命周期綁定中也加上imageCapture。

ImageCaptureConfig可以定制相片尺寸和長寬比例,這里的尺寸和比例跟相機預覽的尺寸比例無關,我測試傳入任何比例都能得到圖片。


        // 2. capture
        ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder()
                .setTargetAspectRatio(new Rational(1,1))
                .setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
                .build();
        final ImageCapture imageCapture = new ImageCapture(imageCaptureConfig);
        viewFinder.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                File photo = new File(getExternalCacheDir() + "/" + System.currentTimeMillis() + ".jpg");
                imageCapture.takePicture(photo, new ImageCapture.OnImageSavedListener() {
                    @Override
                    public void onImageSaved(@NonNull File file) {
                        showToast("saved " + file.getAbsolutePath());
                    }

                    @Override
                    public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
                        showToast("error " + message);
                        cause.printStackTrace();
                    }
                });
                return true;
            }
        });

        CameraX.bindToLifecycle(this, preview, imageCapture);

5 圖片分析

圖片分析名字很高大上,實際上就是圖像數據回調,實時獲取相機的圖像數據,可以自己處理這些圖像。

創(chuàng)建ImageAnalysisConfig和ImageAnalysis這兩個對象,創(chuàng)建一個HandlerThread用于在子線程中處理數據,創(chuàng)建一個ImageAnalysis.Analyzer接口實現(xiàn)類,在analyze(ImageProxy imageProxy, int rotationDegrees)回調方法中就能拿到圖像數據了。當然ImageAnalysis對象也要綁定生命周期。

我這里分析圖像數據用了之前寫的一個工具YUVDetectView,來分析圖像屬于哪種YUV420格式。

        // 3. analyze
        HandlerThread handlerThread = new HandlerThread("Analyze-thread");
        handlerThread.start();

        ImageAnalysisConfig imageAnalysisConfig = new ImageAnalysisConfig.Builder()
                .setCallbackHandler(new Handler(handlerThread.getLooper()))
                .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
                .setTargetAspectRatio(new Rational(2, 3))
//                .setTargetResolution(new Size(600, 600))
                .build();

        ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
        imageAnalysis.setAnalyzer(new MyAnalyzer());

        CameraX.bindToLifecycle(this, preview, imageCapture, imageAnalysis);



    private class MyAnalyzer implements ImageAnalysis.Analyzer {

        @Override
        public void analyze(ImageProxy imageProxy, int rotationDegrees) {
            final Image image = imageProxy.getImage();
            if(image != null) {
                Log.d("chao", image.getWidth() + "," + image.getHeight());
                imageView.input(image);
            }
        }
    }

Github地址

https://github.com/rome753/android-YuvTools
(CameraX代碼在camerax包下面)

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容