AndroidCamera2預覽功能實現
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 實現相機預覽的主要過程。
歡迎加入Android開發技術交流QQ群;701740775
本群提供Android高階開發資料、高階UI、效能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)等相關資料和解答
不懂得問題都可以在本群提出來 還會有職業生涯規劃以及面試指導
進群修改群備註:開發年限-地區-經驗
方便架構師解答問題
相關文章
- 在electron下實現PDF線上預覽功能
- JS 實現全屏預覽 F11功能JS
- APICloud 實現文件下載和預覽功能APICloud
- 利用 ICEpdf 快速實現 pdf 檔案預覽功能
- jq實現上傳頭像並實時預覽功能
- 如何實現檔案轉換與線上預覽功能
- Mac預覽工具使用技巧,Mac預覽功能實用技巧大全Mac
- Mac預覽工具使用教程,Mac預覽功能實用技巧大全Mac
- 實現圖片預覽
- JS 實現全景圖預覽JS
- vue實現圖片預覽Vue
- 文件線上預覽的實現
- Aspose.Cells實現excel預覽Excel
- java實現文件線上預覽工具Java
- uniapp實戰——完成圖片的預覽功能APP
- 用createObjectURL實現本地圖片預覽Object地圖
- php實現網站瀏覽足跡功能PHP網站
- vue中如何實現pdf檔案預覽?Vue
- Android camera2實現預覽Android
- 短視訊平臺開發,圖片上傳和圖片預覽功能實現
- CSS 奇思妙想 | 使用 resize 實現強大的圖片拖拽切換預覽功能CSS
- 直播app系統原始碼,使用element ui隱藏元件實現圖片預覽功能APP原始碼UI元件
- angularjs 實現圖片上傳實時預覽AngularJS
- 標籤實現預載入功能詳解
- 基於 vue.js 實現圖片本地預覽 + 裁剪 + 壓縮 + 上傳的功能(二)Vue.js
- vue整合pdfjs,實現pdf檔案預覽VueJS
- office轉pdf和圖片實現線上預覽
- .NET實現網頁版Office檔案預覽網頁
- SpringMVC實現ajax上傳圖片實時預覽SpringMVC
- ENVIDeepLearning1.1新功能預覽IDE
- 關於筆記軟體Obsidian實時預覽出現前段預覽,後段不預覽的解決方案筆記
- react實現移動端PDF線上預覽外掛React
- Vue3實現excel檔案預覽和列印VueExcel
- 記錄---前端如何實現檔案的線上預覽?前端
- html input type=file 選擇圖片,圖片預覽 純html js實現圖片預覽HTMLJS
- 園子的現代化建設-新功能:釋出合集預覽版
- 100多行程式碼實現js或者jquery版的類似juejin的預覽圖片功能行程JSjQuery
- 直播平臺搭建,使用vue-pdf 實現pdf線上預覽並且自定義預覽框高度Vue