一、资料参考
二、代码
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<!--CAMER相机权限
下面是写出权限-->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CameraDemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--TextureView: 把摄像头捕获到的数据显示到界面上-->
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<Button
android:layout_width="70dp"
android:layout_height="70dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.532"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.948"
android:text="TAKE"
android:onClick="Capture"
></Button>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.example.camerademo;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.util.SparseArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
public class MainActivity extends AppCompatActivity {
TextureView mPreviewview; //预览窗口
//Handler: 异步消息处理机制,可用于主线程与子线程之间传递消息
//nadlerThread:带有消息处理机制的线程
HandlerThread mHandlerThread;
Handler mCameraHandler;
CameraManager manager;
Size mPreviewSize; //最佳的预览尺寸
Size mCaptureSize; //最佳的拍照尺寸
String mCameraId; //要打开的摄像头的id
CameraDevice mCameraDevice; //回调函数中的对象
CaptureRequest.Builder mCaptureRequestBuilder;
CaptureRequest mCaptureRequest;
CameraCaptureSession mCameraCaptureSession;
ImageReader mimageReader;
//根据摄像头夹角确定旋转角度
private static final SparseArray ORIENTATION =new SparseArray();
static{
ORIENTATION.append(Surface.ROTATION_0,90);
ORIENTATION.append(Surface.ROTATION_90,0);
ORIENTATION.append(Surface.ROTATION_180,270);
ORIENTATION.append(Surface.ROTATION_270,180);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPreviewview = findViewById(R.id.textureView);
// onResume();
}
/***********
Cameramanager: 管理摄像头信息
CameraDevice: 摄像头对象,管理session会话,
CameraCaptureSession: 会话,android device向camera device发送请求,以及camera device反馈结果都在会话中进行
CaptureRequest: 请求
CameraMatedata: 反馈结果
*******************/
/* 当打开摄像头或者屏幕旋转的时候需要去检查当前摄像头的状态
这个工作在onResume中进行 onResume方法在activity启动后执行
*/
protected void onResume() {
super.onResume();
startCameraThread(); //打开摄像头线程
/*摄像头没有正常打开的时候预览界面是不能预览内容的
所以这里设置一个监听
*/
if (!mPreviewview.isActivated()) {
//添加监听
mPreviewview.setSurfaceTextureListener(mTextureListener);
}else {
startPreview(); //开始预览
}
}
//这里是创建一个监听器的全局变量,里边的四个函数是自动出现的
TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() {
@Override
//摄像头控件准备好
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
//当 surfaceTexture(摄像头)可用时,设置相机参数并打开摄像头
try {
setupCamera(width, height); //设置摄像头参数(预览界面的宽和高)
} catch (CameraAccessException e) {
throw new RuntimeException(e);
}
openCamera(); //打开摄像头
}
@Override
//尺寸改变
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
}
@Override
//销毁
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
return false;
}
@Override
//更新
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
};
//开启摄像头线程
private void startCameraThread() {
mHandlerThread = new HandlerThread("CameraThread"); //name:"CameraThread"
mHandlerThread.start();
mCameraHandler = new Handler(mHandlerThread.getLooper());
}
//设置摄像头参数(预览界面的宽和高)
private void setupCamera(int width, int height) throws CameraAccessException {
manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); //启动一个系统服务
//遍历手机系统中的摄像头,拿到摄像头ID
for (String cameraID : manager.getCameraIdList()) {
//拿到当前摄像头的一些参数
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraID);
//获得当前摄像头的朝向
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
//如果当前摄像头朝前,那就这个就不要了,直接换下一个
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
//获得当前摄像头的分辨率
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map != null) { //找到摄像头能够输出的。最符合我们当前显示器界面分辨率的最小值
mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height); //括号里是预览界面的尺寸
mCaptureSize=Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new Comparator<Size>() {
@Override
public int compare(Size o1, Size o2) {
return Long.signum(o1.getWidth()*o1.getHeight()-o2.getWidth()*o2.getHeight());
}
});
}
//建立缓冲区,准备存储照片
setupImageReader();
mCameraId = cameraID; //给全局变量id赋值,以便打开相机时使用
break;
}
}
private Size getOptimalSize(Size[] sizeMap, int width, int height) {
List<Size> sizeList = new ArrayList<Size>(); //一个存放分辨率的列表
for (Size option : sizeMap) {
if (width > height) { //横屏
if (option.getWidth() > width && option.getHeight() > height) {
sizeList.add(option);
}
} else { //竖屏
if (option.getWidth() > height && option.getHeight() > width) {
sizeList.add(option);
}
}
}
if (sizeList.size() > 1) {
return Collections.min(sizeList, new Comparator<Size>() {
@Override
public int compare(Size o1, Size o2) {
return Long.signum(o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight());
}
});
}
return sizeMap[0];
}
//打开摄像头
private void openCamera() {
//动态获取权限
String [] permissions={Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE};
//遍历权限
int i=0;
for(String permission:permissions) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
this.requestPermissions(permissions, i++); //弹框
return;
}
}
try {
manager.openCamera(mCameraId, mStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
throw new RuntimeException(e);
}
}
//打开相机的回调函数
CameraDevice.StateCallback mStateCallback=new CameraDevice.StateCallback() {
//摄像头打开,
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice=camera;
startPreview();
}
//摄像头关闭
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
mCameraDevice.close();
mCameraDevice=null;
}
//摄像头发生错误
@Override
public void onError(@NonNull CameraDevice camera, int error) {
mCameraDevice.close();
mCameraDevice=null;
}
};
//开始预览
private void startPreview(){
//建立图像缓冲区
SurfaceTexture mSurfaceTexture=mPreviewview.getSurfaceTexture(); //缓冲区
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight()); //缓冲区尺寸
//得到界面显示对象
Surface previewSurface = new Surface(mSurfaceTexture);
try {
mCaptureRequestBuilder=mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); //预览模板
mCaptureRequestBuilder.addTarget(previewSurface); //把对象放到界面里
//建立通道(CaptureRequest 和 CaptureSession会话)
//asList两个参数:是将绘画的数据流交给界面和保存图片这两个部分进行处理,要将绘画跟要处理的缓冲区建立联系
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface,mimageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
//配置成功
public void onConfigured(@NonNull CameraCaptureSession session) {
mCaptureRequest=mCaptureRequestBuilder.build();
mCameraCaptureSession=session;
//不停发送请求
try {
mCameraCaptureSession.setRepeatingRequest(mCaptureRequest,null,mCameraHandler);
} catch (CameraAccessException e) {
throw new RuntimeException(e);
}
}
@Override
//配置失败
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
}, mCameraHandler);
} catch (CameraAccessException e) {
throw new RuntimeException(e);
}
}
//开始拍照,这个是拍照按钮的响应函数
public void Capture(View view) throws CameraAccessException {
//获取摄像头的请求
CaptureRequest.Builder mCameraBuilder=mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
mCameraBuilder.addTarget(mimageReader.getSurface()); //获取照相缓冲区的数据流
//获取摄像头方向
int rotaion=getWindowManager().getDefaultDisplay().getRotation();
//获取拍照方向
mCameraBuilder.set(CaptureRequest.JPEG_ORIENTATION,(Integer)ORIENTATION.get(rotaion));
//拍照的回调函数
CameraCaptureSession.CaptureCallback mCaptureCallBack=new CameraCaptureSession.CaptureCallback() {
@Override
//拍照完成(onCaptureCompleted当图像捕捉完成,并且结果可用时调用此函数)
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
Toast.makeText(getApplicationContext(),"拍照结束,相片已保存",Toast.LENGTH_LONG).show();
unLockFouces(); //关闭摄像头
super.onCaptureCompleted(session, request, result);
}
};
mCameraCaptureSession.stopRepeating(); //停止绘画请求
mCameraCaptureSession.capture(mCameraBuilder.build(),mCaptureCallBack,mCameraHandler); //capture拍摄单张照片
//获取图像的缓冲区
//获取文件的存储权限及操作
}
private void unLockFouces(){
try {
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
mCameraCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(),null,mCameraHandler);
} catch (CameraAccessException e) {
throw new RuntimeException(e);
}
}
//建立缓冲区,准备保存图片,参数2表示最多存2张
private void setupImageReader(){
mimageReader=ImageReader.newInstance(mCaptureSize.getWidth(),mCaptureSize.getHeight(),ImageFormat.JPEG,2);
//当图片已经准备好了
mimageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
// Image image=reader.acquireLatestImage(); //这样就可以拿到这张图片了
mCameraHandler.post(new ImageSaver(reader.acquireLatestImage())); //acquire..表示图片准备好了,这是就可以调用ImageSaver这个存储过程存储图片
}
},mCameraHandler);
}
private class ImageSaver implements Runnable{
Image mImage;
public ImageSaver(Image image){
mImage=image;
}
public void run(){
ByteBuffer buffer=mImage.getPlanes()[0].getBuffer();
byte[] data=new byte[buffer.remaining()];
buffer.get(data);
String path= Environment.getExternalStorageDirectory()+"/DCIM/CameraV2/";
File mImageFile=new File(path);
if(!mImageFile.exists()){
mImageFile.mkdir(); //如果路径不存在就建一个
}
//文件命名方式:年月日_时分秒
String timeStamp=new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName=path+"IMG_"+timeStamp+".jpg";
try {
FileOutputStream fos=new FileOutputStream(fileName);
fos.write(data,0,data.length); //写出图片
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
三、结果