Android Camera2 預覽功能實現

来源:https://www.cnblogs.com/lightweh/archive/2018/11/21/9994927.html
-Advertisement-
Play Games

1. 概述 最近在做一些關於人臉識別的項目,需要用到 Android 相機的預覽功能。網上查閱相關資料後,發現 Android 5.0 及以後的版本中,原有的 Camera API 已經被 Camera2 API 所取代。 全新的 Camera2 在 Camera 的基礎上進行了改造,大幅提升了 A ...


1. 概述

最近在做一些關於人臉識別的項目,需要用到 Android 相機的預覽功能。網上查閱相關資料後,發現 Android 5.0 及以後的版本中,原有的 Camera API 已經被 Camera2 API 所取代。

全新的 Camera2 在 Camera 的基礎上進行了改造,大幅提升了 Android 系統的拍照功能。它通過以下幾個類與方法來實現相機預覽時的工作過程:

  • CameraManager :攝像頭管理器,主要用於檢測系統攝像頭、打開系統攝像頭等;
  • CameraDevice : 用於描述系統攝像頭,可用於關閉相機、創建相機會話、發送拍照請求等;
  • CameraCharacteristics :用於描述攝像頭所支持的各種特性;
  • CameraCaptureSession :當程式需要預覽、拍照時,都需要先通過 CameraCaptureSession 來實現。該會話通過調用方法 setRepeatingRequest() 實現預覽;
  • CameraRequest :代表一次捕獲請求,用於描述捕獲圖片的各種參數設置;
  • CameraRequest.Builder :負責生成 CameraRequest 對象。

2. 相機預覽

下麵通過源碼來講解如何使用 Camera2 來實現相機的預覽功能。

2.1 相機許可權設置

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

2.2 App 佈局

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"
    tools:context=".MainActivity">
</FrameLayout>
  • fragment_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CameraFragment">

    <com.lightweh.camera2preview.AutoFitTextureView
        android:id="@+id/textureView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

2.3 相機自定義View

public class AutoFitTextureView extends TextureView {

    private int mRatioWidth = 0;
    private int mRatioHeight = 0;

    public AutoFitTextureView(Context context) {
        this(context, null);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }
}

2.4 動態申請相機許可權

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_PERMISSION = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (hasPermission()) {
            if (null == savedInstanceState) {
                setFragment();
            }
        } else {
            requestPermission();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {

        if (requestCode == REQUEST_PERMISSION) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                setFragment();
            } else {
                requestPermission();
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
    // 許可權判斷,當系統版本大於23時,才有必要判斷是否獲取許可權
    private boolean hasPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
        } else {
            return true;
        }
    }
    // 請求相機許可權
    private void requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                Toast.makeText(MainActivity.this, "Camera permission are required for this demo", Toast.LENGTH_LONG).show();
            }
            requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION);
        }
    }
    // 啟動相機Fragment
    private void setFragment() {
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container, CameraFragment.newInstance())
                .commitNowAllowingStateLoss();
    }
}

2.5 開啟相機預覽

首先,在onResume()中,我們需要開啟一個 HandlerThread,然後利用該線程的 Looper 對象構建一個 Handler 用於相機回調。

@Override
public void onResume() {
    super.onResume();
    startBackgroundThread();

    // When the screen is turned off and turned back on, the SurfaceTexture is 
    // already available, and "onSurfaceTextureAvailable" will not be called. In 
    // that case, we can open a camera and start preview from here (otherwise, we 
    // wait until the surface is ready in the SurfaceTextureListener).
    if (mTextureView.isAvailable()) {
        openCamera(mTextureView.getWidth(), mTextureView.getHeight());
    } else {
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    }
}
private void startBackgroundThread() {
    mBackgroundThread = new HandlerThread("CameraBackground");
    mBackgroundThread.start();
    mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}

同時,在 onPause() 中有對應的 HandlerThread 關閉方法。

當屏幕關閉後重新開啟,SurfaceTexture 已經就緒,此時不會觸發 onSurfaceTextureAvailable 回調。因此,我們判斷 mTextureView 如果可用,則直接打開相機,否則等待 SurfaceTexture 回調就緒後再開啟相機。

private void openCamera(int width, int height) {
    if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        return;
    }
    setUpCameraOutputs(width, height);
    configureTransform(width, height);
    Activity activity = getActivity();
    CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    try {
        if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
            throw new RuntimeException("Time out waiting to lock camera opening.");
        }
        manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
    }
}

開啟相機時,我們首先判斷是否具備相機許可權,然後調用 setUpCameraOutputs 函數對相機參數進行設置(包括指定攝像頭、相機預覽方向以及預覽尺寸的設定等),接下來調用 configureTransform 函數對預覽圖片的大小和方向進行調整,最後獲取 CameraManager 對象開啟相機。因為相機有可能會被其他進程同時訪問,所以在開啟相機時需要加鎖。

private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        mCameraOpenCloseLock.release();
        mCameraDevice = cameraDevice;
        createCameraPreviewSession();
    }

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

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int error) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
        Activity activity = getActivity();
        if (null != activity) {
            activity.finish();
        }
    }
};

相機開啟時還會指定相機的狀態變化回調函數 mStateCallback,如果相機成功開啟,則開始創建相機預覽會話。

private void createCameraPreviewSession() {
    try {
        // 獲取 texture 實例
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;

        // 設置 TextureView 緩衝區大小
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

        // 獲取 Surface 顯示預覽數據
        Surface surface = new Surface(texture);

        // 構建適合相機預覽的請求
        mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        
        // 設置 surface 作為預覽數據的顯示界面
        mPreviewRequestBuilder.addTarget(surface);

        // 創建相機捕獲會話用於預覽
        mCameraDevice.createCaptureSession(Arrays.asList(surface),
                new CameraCaptureSession.StateCallback() {

                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                        // 如果相機關閉則返回
                        if (null == mCameraDevice) {
                            return;
                        }

                        // 如果會話準備好則開啟預覽
                        mCaptureSession = cameraCaptureSession;
                        try {
                            // 自動對焦
                          mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

                            mPreviewRequest = mPreviewRequestBuilder.build();
                            // 設置反覆捕獲數據的請求,預覽界面一直顯示畫面
                            mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                    null, mBackgroundHandler);
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onConfigureFailed(
                            @NonNull CameraCaptureSession cameraCaptureSession) {
                        showToast("Failed");
                    }
                }, null
        );
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

以上便是 Camera2 API 實現相機預覽的主要過程。

3. Demo 源碼

Github:Camera2Preview

4. 參考

  • https://github.com/googlesamples/android-Camera2Basic

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • oracle 函數中 decode (state,'false','異常','true','正常') status 等價於 case when state='false' then '異常' when state=‘true’ then '正常' end status 當 status 為 fals ...
  • SQL代碼: SELECT t.* FROM pt_org_info t START WITH t.id = 1 CONNECT BY t.par_id = PRIOR t.id ORDER SIBLINGS BY t.id; 效果圖: ...
  • 此文章對開放數據介面 API 之「全國天氣預報信息數據 API」進行了功能介紹、使用場景介紹以及調用方法的說明,供用戶在使用數據介面時參考之用,並對實戰開發進行了視頻演示。 ...
  • 連接資料庫: try { SqlConnection conn = new SqlConnection(); conn.ConnectionString = "Data Source = (local); Initial Catalog =manage; Integrated Security = ...
  • 本文是對Hadoop2.2.0版本的MapReduce進行詳細講解。請大家要註意版本,因為Hadoop的不同版本,源碼可能是不同的。 以下是本文的大綱: ...
  • 摘要:第一階段:Linux課程講解Linux基礎操作,講的是在命令行下進行文件系統的操作,這是Hadoop學習的基礎,後面的所有視頻都是基於linux操作的。鑒於很多學員沒有linux基礎,特增加該內容,保證零linux基礎入門。如果你從沒有使用過linux,別擔心,本節內容可以讓你入門。Linux ...
  • 第一個函數是計算平面坐標系下,兩點的距離,就是 如果用於計算地球兩點的距離,帶入的參數是角度(0-360度),則計算的單位也是相差的角度,用此角度計算距離不准。緯度距離約111km每度,經度距離在赤道平面上是111km每度,隨緯度的升高逐漸降低為0。 第二個函數是計算球面距離的公式,傳入的參數是經緯 ...
  • Zeus Group 要編寫應用程式“Android”的程式代碼,需要使用特殊的開發環境。 自從創建用於編程的操作系統以來,已經使用了Eclipse和IntelliJ IDEA產品。 為此,已發佈插件,專門用於為Android創建應用程式。 但是,在從Google發佈官方開發環境之後 - Andro ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...