前面兩篇介紹了Camera1和Camera2的使用,發(fā)現(xiàn)Camera API從1到2的變化非常大,Camera2的復雜度提升了不少,官方為了讓我們更容易使用Camera,出了個一個官方的庫cameraview。不過這個庫已經(jīng)Deprecated,官方建議使用Jetpack CameraX 替代。本篇文章就介紹下CameraView和CameraX的使用
CameraView
CameraView的目的就是幫助開發(fā)者能夠快速集成Camera1和Camera2的特性,可以用下面這張表來說明:
API Level | Camera API | Preview View |
---|---|---|
9-13 | Camera1 | SurfaceView |
14-20 | Camera1 | TextureView |
21-23 | Camera2 | TextureView |
24 | Camera2 | SurfaceView |
開發(fā)流程
CameraView定義
xml中定義
<com.google.android.cameraview.CameraView
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:keepScreenOn="true"
android:adjustViewBounds="true"
app:autoFocus="true"
app:aspectRatio="4:3"
app:facing="back"
app:flash="auto"/>
xml中可以配置:
- autoFocus:是否自動對焦
- aspectRatio:預覽畫面比例
- facing:前后攝像頭
- flash:閃光燈模式
增加生命周期
@Override
protected void onResume() {
super.onResume();
mCameraView.start();
}
@Override
protected void onPause() {
mCameraView.stop();
super.onPause();
}
這樣聲明后,就可以完成預覽的工作了
相機狀態(tài)回調(diào)
在xml聲明CameraView后,增加回調(diào)
if (mCameraView != null) {
mCameraView.addCallback(mCallback);
}
...
private CameraView.Callback mCallback
= new CameraView.Callback() {
@Override
public void onCameraOpened(CameraView cameraView) {
Log.d(TAG, "onCameraOpened");
}
@Override
public void onCameraClosed(CameraView cameraView) {
Log.d(TAG, "onCameraClosed");
}
@Override
public void onPictureTaken(CameraView cameraView, final byte[] data) {
Log.d(TAG, "onPictureTaken " + data.length);
Toast.makeText(cameraView.getContext(), R.string.picture_taken, Toast.LENGTH_SHORT)
.show();
getBackgroundHandler().post(new Runnable() {
@Override
public void run() {
File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"picture.jpg");
Log.d(TAG, "onPictureTaken file path: " + file.getPath());
OutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(data);
os.close();
} catch (IOException e) {
Log.w(TAG, "Cannot write to " + file, e);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
// Ignore
}
}
}
}
});
}
};
有三個回調(diào)方法,相機打開,相機關(guān)閉,和拍照。
拍照
mCameraView.takePicture();
就是這么簡單,點擊后拍照,然后回調(diào)中處理圖像數(shù)據(jù)
CameraX
CameraX 是一個 Jetpack 支持庫,目的是簡化Camera的開發(fā)工作,它是基于Camera2 API的基礎(chǔ),向后兼容至 Android 5.0(API 級別 21)。
它有以下幾個特性:
- 易用性,只需要幾行代碼就可以實現(xiàn)預覽和拍照
- 保持設(shè)備的一致性,在不同相機設(shè)備上,對寬高比、屏幕方向、旋轉(zhuǎn)、預覽大小和高分辨率圖片大小,做到都可以正常使用
- 相機特性的擴展,增加人像、HDR、夜間模式和美顏等功能
開發(fā)流程
庫引用
目前CameraX最新版本是1.0.0-alpha06
,在app的build.gradle引用:
dependencies {
// CameraX core library.
def camerax_version = "1.0.0-alpha06"
implementation "androidx.camera:camera-core:${camerax_version}"
// If you want to use Camera2 extensions.
implementation "androidx.camera:camera-camera2:${camerax_version}"
def camerax_view_version = "1.0.0-alpha03"
def camerax_ext_version = "1.0.0-alpha03"
//other
// If you to use the Camera View class
implementation "androidx.camera:camera-view:$camerax_view_version"
// If you to use Camera Extensions
implementation "androidx.camera:camera-extensions:$camerax_ext_version"
}
因為CameraX是一個 Jetpack 支持庫,相機的打開和釋放都是使用了Jetpack的Lifecycle來進行處理。
預覽
預覽參數(shù)設(shè)置,使用PreviewConfig.Builder()實現(xiàn):
PreviewConfig config = new PreviewConfig.Builder()
.setLensFacing(CameraX.LensFacing.BACK)
.setTargetRotation(mTextureView.getDisplay().getRotation())
.setTargetResolution(new Size(640, 480))
.build();
Preview preview = new Preview(config);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
@Override
public void onUpdated(@NonNull Preview.PreviewOutput output) {
if (mTextureView.getParent() instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) mTextureView.getParent();
viewGroup.removeView(mTextureView);
viewGroup.addView(mTextureView, 0);
mTextureView.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
}
}
});
//lifecycle
CameraX.bindToLifecycle(this, preview);
PreivewConfig.Builder可以設(shè)置的屬性很多,這里只設(shè)置了攝像頭、旋轉(zhuǎn)方向、預覽分辨率,還有很多其他方法,大家可以自行試驗。
在preview回調(diào)監(jiān)聽中,把output的SurfaceTexture設(shè)置到mTextureView中,實現(xiàn)圖像預覽,最后增加Lifecycle的綁定。
拍照
ImageCaptureConfig captureConfig = new ImageCaptureConfig.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.setTargetRotation(getWindowManager().getDefaultDisplay().getRotation())
.build();
ImageCapture imageCapture = new ImageCapture(captureConfig);
mTakePicture.setOnClickListener((view) -> {
final File file = new File(getExternalMediaDirs()[0], System.currentTimeMillis() + ".jpg");
Log.d("DEBUG", "##### file path: " + file.getPath());
imageCapture.takePicture(file, ContextCompat.getMainExecutor(getApplicationContext()), new ImageCapture.OnImageSavedListener() {
@Override
public void onImageSaved(@NonNull File file) {
Log.d("DEBUG", "##### onImageSaved: " + file.getPath());
}
@Override
public void onError(@NonNull ImageCapture.ImageCaptureError imageCaptureError, @NonNull String message, @Nullable Throwable cause) {
Log.d("DEBUG", "##### onError: " + message);
}
});
});
CameraX.bindToLifecycle(this, preview, imageCapture);
拍照的參數(shù)通過ImageCaptureConfig.Builder
設(shè)置,這里只設(shè)置了圖片寬高比、拍攝模式和旋轉(zhuǎn)方向,還有很多其他方法,大家可以自行試驗。
真正調(diào)用拍照的方法:
- takePicture(OnImageCapturedListener):此方法為拍攝的圖片提供內(nèi)存緩沖區(qū)。
- takePicture(File, OnImageSavedListener):此方法將拍攝的圖片保存到提供的文件位置。
- takePicture(File, OnImageSavedListener, Metadata):此方法可用于指定要嵌入已保存文件的 Exif 中的元數(shù)據(jù)。
例子調(diào)用的是takePicture(File, OnImageSavedListener),直接存為文件。最后再增加Lifecycle的綁定。
圖片分析
ImageAnalysisConfig analysisConfig = new ImageAnalysisConfig.Builder()
.setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
.build();
ImageAnalysis imageAnalysis = new ImageAnalysis(analysisConfig);
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(getApplicationContext()),
new LuminosityAnalyzer());
CameraX.bindToLifecycle(this, preview, imageCapture, imageAnalysis);
...
private class LuminosityAnalyzer implements ImageAnalysis.Analyzer {
private long lastAnalyzedTimestamp = 0L;
@Override
public void analyze(ImageProxy image, int rotationDegrees) {
final Image img = image.getImage();
if (img != null) {
Log.d("DEBUG", img.getWidth() + "," + img.getHeight());
}
}
}
圖片分析,不是必要的步驟,但是ImageAnalysis,可以對每幀圖像進行分析。
設(shè)置參數(shù)通過ImageAnalysisConfig.Builder()
,這里只設(shè)置了ImageReaderMode
,它有兩種模式:
- 阻止模式(ImageReaderMode.ACQUIRE_NEXT_IMAGE):就是Camera2中的acquireNextImage(),獲取下一個最新的可用Image
- 非阻止模式(ImageReaderMode.ACQUIRE_LATEST_IMAGE):Camera2中的acquireLatestImage(),獲得圖像隊列中最新的圖片,并且會清空隊列,刪除已有的舊的圖像
最后還是增加Lifecycle的綁定。CameraX的使用也非常簡單,把Camera2中復雜的API封裝到統(tǒng)一的config中,只需要幾行代碼,就實現(xiàn)需要的功能。
文章中涉及到的代碼