Android CameraX概述
在本教程中,我们将详细讨论Android CameraX API。
CameraX是Google IO 2019中最受欢迎的版本之一。
让我们看看它为我们提供的功能。
什么是Android CameraX?
开发Camera Apps一直很困难。
掌握Camera API绝非易事。
因此,Jetpack支持库中引入的CameraX是一个喘息的机会。
它旨在简化开发过程。
除了API的复杂性,在开发相机应用程序时,我们还必须解决许多情况,例如:
- 操作系统版本
- 设备型号规范– Android电子市场是零散的,具有各种各样的设备配置。
在不同的手机(尤其是三星)上,同一应用程序的行为可能有所不同。
在这方面,CameraX努力使设备之间的一致性更高。
CameraX基本上在后台使用Camera2 API,但实现过程更好。
向下兼容,直到Android Lollipop(SDK 21)。
CameraX通过包括Camera 2 Legacy Layer,提供了与已弃用的Camera 1 API相同的一致性。
Google如何做到这一点?
将Automated CameraX测试实验室与各种制造商的许多设备一起使用。
根据Google IO 2019,CameraX解决了以下问题。
资料来源:Google IO 2019
Android CameraX:抽象Camera2 API
Camera2 API为我们提供了许多对传感器的精细控制。
更重要的是,它需要大量样板代码。
为了访问摄像机的硬件和驱动程序,它需要与HAL(硬件加速层)进行通信。
CameraX本质上所做的就是抽象所有这些。
因此,由于具有更高的可读性和更少的样板代码,CameraX提供了易用性。
Android CameraX:用例
CameraX提出了一种基于用例的方法来专注于您需要完成的任务,而不是花费时间来管理特定于设备的配置。
三个核心用例是:
预览–您所看到的。
相机供稿。图像分析–您的工作。
处理相机供稿。图像捕获–您保留的内容。
捕获照片/视频。
Android CameraX扩展
扩展基本上使我们仅需几行代码就可以直接在自定义相机应用程序中使用设备本机相机功能。
如果设备支持的话,肖像模式,深度,散景效果等功能可以轻松集成到用例中。
CameraX具有生命周期意识。
因此,除了在onResume()和onPause()中启动和停止摄像机预览以及其他用例之外,我们还可以使用CameraX.bindToLifecycle()来做到这一点。
聊够了。
现在,让我们深入研究CameraX代码。
在下面的部分中,我们将实现两个用例-预览和图像捕获。
图像分析器将在单独的教程中介绍。
CameraX示例项目结构
Android Camerax项目结构
CameraX实施代码
首先,设置" build.gradle":
implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
def cameraxVersion = "1.0.0-alpha02"
implementation "androidx.camera:camera-core:${cameraxVersion}"
implementation "androidx.camera:camera-camera2:${cameraxVersion}"
我们需要在AndroidManifest.xml文件中添加对Camera和External Storage的权限。
下面给出了activity_main.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextureView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
<ImageButton
android:id="@+id/imgCapture"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_margin="24dp"
app:srcCompat="@android:drawable/ic_menu_camera"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
</androidx.constraintlayout.widget.ConstraintLayout>
下面给出了MainActivity.java类的代码。
package com.theitroad.androidcamerax;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraX;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureConfig;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.os.Bundle;
import android.os.Environment;
import android.util.Rational;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private int REQUEST_CODE_PERMISSIONS = 101;
private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};
TextureView textureView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureView = findViewById(R.id.view_finder);
if(allPermissionsGranted()){
startCamera(); //start camera if permission has been granted by user
} else{
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
}
private void startCamera() {
CameraX.unbindAll();
Rational aspectRatio = new Rational (textureView.getWidth(), textureView.getHeight());
Size screen = new Size(textureView.getWidth(), textureView.getHeight()); //size of the screen
PreviewConfig pConfig = new PreviewConfig.Builder().setTargetAspectRatio(aspectRatio).setTargetResolution(screen).build();
Preview preview = new Preview(pConfig);
preview.setOnPreviewOutputUpdateListener(
new Preview.OnPreviewOutputUpdateListener() {
//to update the surface texture we have to destroy it first then re-add it
@Override
public void onUpdated(Preview.PreviewOutput output){
ViewGroup parent = (ViewGroup) textureView.getParent();
parent.removeView(textureView);
parent.addView(textureView, 0);
textureView.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
}
});
ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder().setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.setTargetRotation(getWindowManager().getDefaultDisplay().getRotation()).build();
final ImageCapture imgCap = new ImageCapture(imageCaptureConfig);
findViewById(R.id.imgCapture).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File file = new File(Environment.getExternalStorageDirectory() + "/" + System.currentTimeMillis() + ".png");
imgCap.takePicture(file, new ImageCapture.OnImageSavedListener() {
@Override
public void onImageSaved(@NonNull File file) {
String msg = "Pic captured at " + file.getAbsolutePath();
Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
}
@Override
public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
String msg = "Pic capture failed : " + message;
Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
if(cause != null){
cause.printStackTrace();
}
}
});
}
});
//bind to lifecycle:
CameraX.bindToLifecycle((LifecycleOwner)this, preview, imgCap);
}
private void updateTransform(){
Matrix mx = new Matrix();
float w = textureView.getMeasuredWidth();
float h = textureView.getMeasuredHeight();
float cX = w/2f;
float cY = h/2f;
int rotationDgr;
int rotation = (int)textureView.getRotation();
switch(rotation){
case Surface.ROTATION_0:
rotationDgr = 0;
break;
case Surface.ROTATION_90:
rotationDgr = 90;
break;
case Surface.ROTATION_180:
rotationDgr = 180;
break;
case Surface.ROTATION_270:
rotationDgr = 270;
break;
default:
return;
}
mx.postRotate((float)rotationDgr, cX, cY);
textureView.setTransform(mx);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if(requestCode == REQUEST_CODE_PERMISSIONS){
if(allPermissionsGranted()){
startCamera();
} else{
Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
finish();
}
}
}
private boolean allPermissionsGranted(){
for(String permission : REQUIRED_PERMISSIONS){
if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
return false;
}
}
return true;
}
}
在" PreviewConfig"中,我们配置预览,即实时摄像机供稿。
在构建器中,我们可以设置纵横比,镜头前后或者目标分辨率等内容。
预览显示在" TextureView"上。
" ImageCaptureConfiguration.Builder()"是捕获时图像的配置。
我们设置了不同的配置,例如MIN_LATENCY或者MAX_QUALITY。

