首先布局文件里创建一个TextureView用于显示,一个ImageButton用于拍照,一个ImageButton用于切换
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/textureview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_alignParentBottom="true">
<ImageButton
android:id="@+id/takepicture_button"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerInParent="true"
android:visibility="visible"/>
<ImageButton
android:id="@+id/switch_buttont"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="20dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:visibility="visible"/>
</RelativeLayout>
</RelativeLayout>
1.全局变量
private TextureView textureView;
private String cameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);//摄像头id
private Size previewSize;//预览尺寸
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
//键值对存储的容器
// 摄像头状态回调
private CameraDevice.StateCallback stateCallback;
private CameraDevice cameraDevice;
private ImageReader imageReader;
private Surface previewsurface;
private CaptureRequest.Builder previewRequestBuilder;
private CameraCaptureSession cameraCaptureSession;
private CaptureRequest previewRequest;
private ImageButton tekepicture_button;
private ImageButton vidiorecode_button;
private static final SparseIntArray ORIENTATION = new SparseIntArray();
private static final String[] permissions = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO};
private List<String> permissionList = new ArrayList<>();
private ImageButton switch_button;
2.加载控件,设置监听
protected void onCreate(Bundle savedInstanceState) {
Log.d("--ActivityLife--", "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureView = (TextureView) findViewById(R.id.textureview);
tekepicture_button = (ImageButton) findViewById(R.id.takepicture_button);
tekepicture_button.setOnClickListener(MainActivity.this);
switch_button = (ImageButton) findViewById(R.id.switch_buttont);
switch_button.setOnClickListener(MainActivity.this);
permissionApply();//获取权限
hideActionBar();//隐藏活动标题栏导航栏
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.takepicture_button:
capture();
break;
case R.id.switch_buttont:
switchCamera();
break;
}
}
//onResume里重新打开摄像机:解决活动在暂停后再次打开时停止的问题
@Override
protected void onResume() {
super.onResume();
if (textureView.isAvailable()) {
openCamera();
} else {
setTextureViewListener();
}
}
3.权限申请(三个权限)
private void permissionApply() {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(MainActivity.this, permission) != PackageManager.PERMISSION_GRANTED) {
permissionList.add(permission);
}
}
if (!permissionList.isEmpty()) {
ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1000);
}
}
权限申请的回调函数
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1000) {
//权限请求失败
if (grantResults.length > 0) {
boolean flag = textureView.isAvailable();
// Log.d("TEXTURE--", String.valueOf(flag));
//存放没授权的权限
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
String permission = permissions[i];
if (grantResult != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permission);
}
}
//如果存在没有申请过的权限,进行权限申请
if (!deniedPermissions.isEmpty()) {
Toast.makeText(MainActivity.this, "NOT GRANTED PERMISSION", Toast.LENGTH_SHORT).show();
finish();
}
}
}
}
4.为Texture设置监听
//为textureView设置一个监听,当onSurfaceTextureAvailable即Texture可用的时候
// 获取预览尺寸checkCamera(),
// 配置预览尺寸configureTransform(),
// 打开相机
private void setTextureViewListener() {
//textureView设置一个监听
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
//在textureview可用的时候
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
checkCamera(width, height);//获取预览尺寸
configureTransform(width, height);//把矩阵添加到TextureView,配置预览尺寸
openCamera();//打开相机
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
}
});
}
5.checkCamera()将设备可用的尺寸输出,寻找一个适合的预览尺寸:遍历相机摄像头,
- cameraManager.getCameraIdList()方法能获取设备的所有相机id列表,
- 通过cameraManager.getCameraCharacteristics(id)方法获取相机的信息提供者CameraCharacteristics,CameraCharacteristics是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING; 判断闪光灯是否可用的FLASH_INFO_AVAILABLE; 获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等等;
- 通过cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)获取map;
- 通过map.getOutPutSizes()方法获取预览的所有尺寸列表;该方法会要求你传递一个 Class 类型,然后根据这个类型返回对应的尺寸列表,如果给定的类型不支持,则返回 null, ImageReader:常用来拍照或接收 YUV数据,MediaRecorder:常用来录制视频,MediaCodec:常用来录制视频,SurfaceHolder:常用来显示预览画面,SurfaceTexture:常用来显示预览画面。宽是长边,高是短边;
-通过自定义的getOptimalSize()方法进行尺寸的匹配,获取最适合的尺寸
private void checkCamera(int width, int height) {
//获取摄像头的管理者
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//遍历摄像头
for (String id : cameraManager.getCameraIdList()) {
// 默认打开后置摄像头 - 忽略前置摄像头
if (!cameraId.equals(id)) continue;
// CameraCharacteristics 是一个只读的相机信息提供者,
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
//通过 CameraCharacteristics 获取该设备支持的所有预览尺寸
// (先通过 SCALER_STREAM_CONFIGURATION_MAP 获取 StreamConfigurationMap 对象,
// 然后通过 StreamConfigurationMap.getOutputSizes() 方法获取尺寸列表
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//Camera2 则是把尺寸信息设置给 Surface,例如接收预览画面的 SurfaceTexture,或者是接收拍照图片的 ImageReader,
// 相机在输出图像数据的时候会根据 Surface 配置的 Buffer 大小输出对应尺寸的画面。
backOrientation();
//对ORIENTATION赋值,默认为后置,因此旋转90
previewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
//获取TextureView的尺寸配置
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 选择sizeMap中大于并且最接近width和height的size
private Size getOptimalSize(Size[] sizeMap, int width, int height) {
List<Size> sizeList = new ArrayList<>();
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() > 0) {
return Collections.min(sizeList, new Comparator<Size>() {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
}
});
}
return sizeMap[0];
}
6.configureTransform(width, height)把矩阵添加到TextureView,配置预览尺寸(TextureView和刚刚得到的最适合的尺寸)
private void configureTransform(int viewWidth, int viewHeight) {
if (null == textureView || null == previewSize) {
return;
}
//角度
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / previewSize.getHeight(),
(float) viewWidth / previewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
textureView.setTransform(matrix);
}
7.openCamera();//打开相机
- CameraManager cameraManager=getSystemService(Context.CAMERA_SERVICE);
- 确定是否打开了相机权限
- 利用cameraManager.openCamera()方法打开相机
- 由于openCamera()方法需要回调函数,因此补充新建一个stateCallback,在方法里的onOpened()中获取打开的设备cameraDevices并打开预览startPreview()
private void openCamera() {
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
//打开相机,第一个参数指示打开哪个摄像头,
// 第二个参数stateCallback为相机的状态回调接口,
// 第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevices) {
cameraDevice = cameraDevices;
startPreview();//开始预览
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
}
};
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
finish();
}
try {
cameraManager.openCamera(cameraId, stateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
8.startPreView()开始预览
-
创建imageReader,configImageReader(); ImageReader.newInstance()
-
新建imageReader对象,前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几张;
-
给imageReader设置监听:imagerReader.setOnImageAvailableListener(),监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理;
-
监听者:new ImageReader.OnImageAvailableListener的回调函数nImageAvailable():获取生成的图片image,开启存储图片的线程ImageSave(线程在拍照后才会存储图片,线程的代码在拍照后面),定义一个intent用于发送广播通知系统更新相册。
private void configImageReader() {
//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据, Image 对象池的大小
imageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1);
//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理
//当有图像数据生成的时候,ImageReader 会通过通过 ImageReader.OnImageAvailableListener.onImageAvailable() 方法通知我们
imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
Image image = imageReader.acquireLatestImage();//获取生成的图片
// if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=)
new Thread(new ImageSave(image)).start();// 保存图片
}
}, null);
}
-
创建Surface对象先通过TextureView.getSurfaceTexture()方法获取SurfaceTexture对象,设置缓冲区大小,并传入SurfaceTexture创建Surface对象;
-
发出预览请求,通过cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)请求,获得一个CaptureRequest.Builder对象,CmeraDevices.TEMPLATE_PREVIEW常量为预览请求的常量,后面拍照传入的常量换为CmeraDevices.TEMPLATE_STILL_CAPTURE就可以,通过Builder将surface设置,并build()获取CaptureRequest对象;
-
通过cameraDevice.createCaptureSession(Array.asList(previewsurface,imageReader.getSurface()),sessionStateCallback,null)创建捕捉会话,补充回调函数CameraCaptureSession.StateCallback对象,会话创建成功后获取会话, repeatPreview()开启重复预览;
//开始预览
private void startPreview() {
//创建imageReager,设置监听
configImageReader();
//设置TextureView的缓冲区大小并且创建 Surface 对象
SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
previewsurface = new Surface(surfaceTexture);
//发出预览请求
try {
Log.d("OPENCAMEREA--","STARTPREVIEW()");
previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
e.printStackTrace();
}
//设置预览的显示界面
previewRequestBuilder.addTarget(previewsurface);
previewRequest = previewRequestBuilder.build();//请求
//创建相机捕获会话,
// 第一个参数是捕获数据的输出Surface列表,
// 第二个参数用于监听 Session 状态的CameraCaptureSession.StateCallback对象,
// (是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,)
// 第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行,
// (用于执行 CameraCaptureSession.StateCallback 的 Handler 对象,可以是异步线程的 Handler,也可以是主线程的 Handler)
try {
CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession CaptureSession) {
cameraCaptureSession = CaptureSession;//会话
repeatPreview();//重复预览
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
};
cameraDevice.createCaptureSession(Arrays.asList(previewsurface, imageReader.getSurface()), sessionStateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
通过cameraCaptureSession.setRepeatingRequest()开启,补充回调函数CameraCaptureSession.CaptureCallback previewCaptureCallback;
//重复预览
private void repeatPreview() {
CameraCaptureSession.CaptureCallback previewCaptureCallback = new CameraCaptureSession.CaptureCallback() {
};
try {
cameraCaptureSession.setRepeatingRequest(previewRequest, previewCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
9.拍照
- 创建捕获请求,依旧利用cameraDevice.creatCaptureRequest()方法,传入的常量换为CmeraDevices.TEMPLATE_STILL_CAPTURE,
获取屏幕的方向rotation=getWindowManager().getDefaultDisplay().getRotation(),将Surface对象和imageReader传入,对Builder进行配置,停止预览,通过会话cameraCaptureSession的capture捕捉,参数为一个通过build()方法创建的CaptureRequest对象,回调,null,补充回调函数,重新开始重复预览repeatPreview();
private void capture() {
try {
//首先我们创建请求拍照的CaptureRequest
final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
//获取屏幕方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
//一个 CaptureRequest 除了需要配置很多参数之外,还要求至少配置一个 Surface(任何相机操作的本质都是为了捕获图像),
captureBuilder.addTarget(previewsurface);
captureBuilder.addTarget(imageReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
cameraCaptureSession.stopRepeating();
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
Toast.makeText(MainActivity.this, "图片已保存", Toast.LENGTH_SHORT).show();
repeatPreview();
}
};
cameraCaptureSession.capture(captureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
拍照捕获后保存图片的线程ImageSave
-
获取图片,由于 在 Image 对象里图像数据根据不同格式被划分多个部分分别存储在单独的 Plane 对象里,要先通过调用
Image.getPlanes() 方法获取所有的 Plane 对象的数组,最后通过 Plane.getBuffer() 获取每一个
Plane 里存储的图像数据。ByteBuffer buffer=image.getPlanes()[0].getBuffer(); -
获取图像数据,利用byte的数组存储,由于在前置摄像的图片会呈现镜面,因此创建Bitmap对象,用以将镜像翻转,BitmapFactory.decodeByteArray(data,0,datda.length)创建对象,创建File对象用以存储照片,利用FileOutPut,并通过Bitmap.compress()写入,
-
释放资源,当我们不再需要使用某一个Image 对象的时候记得通过该方法释放资源,因为 Image 对象实际上来自于一个创建
ImageReader时就确定大小的对象池,如果我们不释放它的话就会导致对象池很快就被耗光,并且抛出一个异常。
public class ImageSave implements Runnable {
private Image image;
public ImageSave(Image getimage) {
image = getimage;
}
@Override
public void run() {
//在 Image 对象里图像数据根据不同格式被划分多个部分分别存储在单独的 Plane 对象里,
//通过调用 Image.getPlanes() 方法获取所有的 Plane 对象的数组
//最后通过 Plane.getBuffer() 获取每一个 Plane 里存储的图像数据。
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];//remaining()返回剩余的可用长度
buffer.get(data);
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
if ("1".equals(cameraId)) {
Matrix m = new Matrix();
m.postScale(-1, 1); // 镜像水平翻转
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
}
// File imageFile = new File(getExternalCacheDir(),System.currentTimeMillis()+".jpg");
//String path = Environment.getExternalStorageDirectory().getAbsolutePath()+ "/DCIM";
File imageFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)+"/Camera", System.currentTimeMillis() + ".jpg");
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(imageFile);
// fileOutputStream.write(data, 0, data.length);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera";
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(path));
intent.setData(uri);
sendBroadcast(intent);//发送广播通知系统更新相册
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
image.close();
//当我们不再需要使用某一个 Image 对象的时候记得通过该方法释放资源,
// 因为 Image 对象实际上来自于一个创建 ImageReader 时就确定大小的对象池,
// 如果我们不释放它的话就会导致对象池很快就被耗光,并且抛出一个异常。
}
}
}
10.前后摄像头转换
- 前后摄像的角度旋转不一样,因此在切换后需要对ORIENTATION更新,前置旋转270,后置90
//前置拍摄时,照片旋转270
private void frontOrientation() {
//前置时,照片旋转270
ORIENTATION.append(Surface.ROTATION_0, 270);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 90);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
//后置拍摄时,照片旋转90
private static void backOrientation() {
//后置时,照片旋转90
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
- 对当前相机id进行判断和改变,依旧要利用cameraManager.getCameraCharacteristics获取信息提供,利用characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURTION_MAP)获取StreamConfigurationMap
map
private void switchCamera() {
//获取摄像头的管理者
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
for (String id : cameraManager.getCameraIdList()) {
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
previewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), textureView.getWidth(), textureView.getHeight());
if (cameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK)) && characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
cameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
cameraDevice.close();
backOrientation();
openCamera();
break;
} else if (cameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_FRONT)) && characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {
cameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
cameraDevice.close();
frontOrientation();
openCamera();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}