通过Android官方的Camera2Basic工程源码,总结出的Android摄像头获取图像的最简编程实现:
步骤1: 申请权限
因摄像头是一个隐私性非常强的设备,所以需要弹框向用户申请权限。
在工程配置文件AndroidManifest.xml中标明使用权限:
<uses-permission android:name="android.permission.CAMERA"/>
在代码中弹框申请权限:
String [] permissions = {
Manifest.permission.CAMERA,
};
ActivityCompat.requestPermissions(this, permissions, 0);
步骤2: 准备子线程
因摄像头相关的操作是非常消耗资源的耗时工作,因此最好在一个子线程里执行。
HbgThread = new HandlerThread("CameraThread");//创建HandlerThread对象,指定线程名
bgThread.start();//子线程启动,注意它的run方法是没有指定,需要通过Handler把run方法代码交过来执行
bgHandler = new Handler(bgThread.getLooper());//创建用于与bgThread通信的Handler对象,通过它的post方法把任务发到子线程执行
// bgHandler.post(new Runnable() { ... } );
//摄像头操作相关的方法函数只需要提供bgHandler对象就会自动post相关的操作代码在bgThread里执行
步骤3. 打开摄像头
创建Android摄像头打开操作的回调方法对象,用于指定摄像头成功打开后、关闭后和打开失败后作什么操作。
private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
@Override //摄像头成功打开后的操作
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.e("myinfo", "摄像头成功打开");
}
@Override //摄像头关闭后的操作
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
}
@Override //摄像头出错后的操作
public void onError(@NonNull CameraDevice cameraDevice, int i) {
}
};
获取当前Android系统的摄像头管理器,并打开指定的摄像头。
//获取当前系统的摄像头管理器
CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
String [] ids;
//获取当前系统里所有摄像头设备的ID
ids = cameraManager.getCameraIdList();
//输出所有摄像头设备的ID, 一般"0"表示前摄像头, "1"表示后摄像头
for (int i = 0; i < ids.length; i++)
Log.e("myinfo", "id = " + ids[i]);
//打开摄像头,指定摄像头打开操作的回调方法对象,并传入可下发任务到子线程执行的Handler对象
cameraManager.openCamera(ids[0], cameraCallback, bgHandler);
步骤4. 创建摄像头设备的捕捉会话对象
摄像头设备的捕捉会话对象用于向摄像头申请捕捉图像、打开闪光灯等操作的命令通道。
首先创建摄像头设备会话对象的回调方法对象,用于指定会话通道配置成功或失败后作什么操作。
private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {
@Override //会话对象创建配置成功后的操作
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e("myinfo", "摄像头会话创建成功!");
}
@Override //会话对象创建配置失败后的操作
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
};
在摄像头成功打开后触发的回调方法onOpened中创建一个接收图像数据的ImageReader对象,
并创建一个摄像头会话对象,指定使用会话的回调方法对象,并传入可下发任务到子线程执行的Handler对象:
private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
@Override //摄像头成功打开后的操作
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.e("myinfo", "摄像头成功打开");
//创建接收摄像头图像的ImageReader对象,指定图像分辨率、编码格式、图像队列中最多存多少张图像
imageReader = ImageReader.newInstance(800, 600, ImageFormat.JPEG, 3);
camDevice = cameraDevice;//记录已成功打开的摄像头设备对象
//创建会话对象
camDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureCallback
, bgHandler);
}
...
}
步骤5. 发出捕捉图像请求
为ImageReader对象创建一个监听器对象,指定当接收到摄像头图像后作什么操作。
private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码
}
};
在会话对象创建并配置成功后触发的onConfigured方法中发出捕捉图像请求, 并设置ImageReader对象使用imgListener监听器对象。
private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {
@Override //会话对象创建配置成功后的操作
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e("myinfo", "摄像头会话创建成功!");
//创建捕捉请求对象,指定请求摄像的预览图像
CaptureRequest.Builder builder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(imageReader.getSurface());//指定捕捉到图像数据由imageReader对象处理
//当ImageReader对象接收到图像后触发OnImageAvailableListener方法,并由bgThread子线程执行方法里的代码
imageReader.setOnImageAvailableListener(imgListener, bgHandler);
//通过会话对象提交请求,并且设置是循环的请求
cameraCaptureSession.setRepeatingRequest(builder.build(), null, bgHandler);
}
...
}
步骤6. 处理捕捉到的摄像头图像
当ImageReader对象接收到图像后触发OnImageAvailableListener方法,在此方法处理图像数据,如进行网络传输、存储、显示等操作。
private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码
Image img = imageReader.acquireNextImage();//获取出图像
ByteBuffer buffer = img.getPlanes()[0].getBuffer();//获取图像数据
byte [] bytes = new byte[buffer.remaining()];
buffer.get(bytes);//图像数据存入bytes数组里
img.close();//关闭图像
//把bytes数组图像数据转成Bitmap图像,并在ImageView组件上显示
Bitmap map = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
//让主线程设置显示图像
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(map);
}
});
}
};
获取摄像头图像并在ImageView组件上显示的完整工程代码:
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
private Button btnStart;
private HandlerThread bgThread;//可通过Handler下发任务的子线程
private Handler bgHandler;//用于与bgThread通信的Handler对象
private ImageReader imageReader;//用于接收图像的对象名
private CameraDevice camDevice; //记录已打开的摄像头设备对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
btnStart = findViewById(R.id.btnStart);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
initializeCamera(); //摄像头初始化
}
});
requestPermissions();//弹框请求权限
}
private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码
Image img = imageReader.acquireNextImage();//获取出图像
ByteBuffer buffer = img.getPlanes()[0].getBuffer();//获取图像数据
byte [] bytes = new byte[buffer.remaining()];
buffer.get(bytes);//图像数据存入bytes数组里
img.close();//关闭图像
//把bytes数组图像数据转成Bitmap图像
Bitmap map = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
//让主线程设置显示图像
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(map);
}
});
}
};
private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {
@Override //会话对象创建配置成功后的操作
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e("myinfo", "摄像头会话创建成功!");
try {
//3.创建捕捉请求对象,指定请求摄像的预览图像
CaptureRequest.Builder builder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(imageReader.getSurface());//指定捕捉到图像数据由imageReader对象处理
//当ImageReader对象接收到图像后触发OnImageAvailableListener方法,并由bgThread子线程执行方法里的代码
imageReader.setOnImageAvailableListener(imgListener, bgHandler);
//通过会话对象提交请求,并且设置是循环的请求
cameraCaptureSession.setRepeatingRequest(builder.build(), null, bgHandler);
}catch (Exception e) {
e.printStackTrace();
}
}
@Override //会话对象创建配置失败后的操作
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
};
private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
@Override //摄像头成功打开后的操作
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.e("myinfo", "摄像头成功打开");
//创建接收摄像头图像的ImageReader对象,指定图像分辨率、编码格式、图像队列中最多存多少张图像
imageReader = ImageReader.newInstance(800, 600, ImageFormat.JPEG, 3);
camDevice = cameraDevice;//记录已成功打开的摄像头设备对象
try {
//2.创建会话对象
camDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureCallback
, bgHandler);
}catch (Exception e) {
e.printStackTrace();
}
}
@Override //摄像头关闭后的操作
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
}
@Override //摄像头出错后的操作
public void onError(@NonNull CameraDevice cameraDevice, int i) {
}
};
@SuppressLint("MissingPermission")
public void initializeCamera() { //初始化摄像头
bgThread = new HandlerThread("CameraThread");//创建HandlerThread对象,指定线程名
bgThread.start();//子线程启动,注意它的run方法是没有指定,需要通过Handler把run方法代码交过来执行
bgHandler = new Handler(bgThread.getLooper());//创建用于与bgThread通信的Handler对象,通过它的post方法把任务发到子线程执行
// bgHandler.post(new Runnable() { ... } );
//摄像头操作相关的方法函数只需要提供bgHandler对象就会自动post相关的操作代码在bgThread里执行
//获取当前系统的摄像头管理器
CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
String [] ids;
try {
//获取当前系统里所有摄像头设备的ID
ids = cameraManager.getCameraIdList();
//输出所有摄像头设备的ID, 一般"0"表示前摄像头, "1"表示后摄像头
for (int i = 0; i < ids.length; i++)
Log.e("myinfo", "id = " + ids[i]);
//1.打开摄像头
cameraManager.openCamera(ids[0], cameraCallback, bgHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
public void requestPermissions() {
String [] permissions = {
Manifest.permission.CAMERA,
};
ActivityCompat.requestPermissions(this, permissions, 0);
}
}