此文章我会用Camera2 和CameraX 分别实现预览拍照录制视频
目录
此文章我会用Camera2 和CameraX 分别实现预览拍照录制视频
Camera2
APP实现 Camera2 需要提前声明
相机方向

相机相对于手机屏幕默认方向不一致:
前置摄像头(指向与显示屏方向相同的摄像头)的传感器相对于手机旋转 270 度(顺时针),以符合 Android 兼容性定义

手机的传感器是顺时针旋转的 左横屏就是90度
手机的传感器是顺时针旋转的 右横屏就是270度或者-90度
Camera层级架构
Camera 只是Android的一部分 所以框架层级整体跟Android一样为
Applications 应用层 -----------------------------对应camera APP
Framework层 -----------------------------对应Java Framework
Libraries 系统运行库层 -----------------------------对应Native Framework (CameraService)
Hardwre Abstraction layer HAL硬件抽象层-----------------------------对应Camera Provider
Linux Kernel 内核层 -----------------------------对应Camera Driver

Camera根据Android 架构从上至下可分为
1)Applications: 最上层的应用,编译后生成Camera APK;
2)Application Framework: 主要为Applications提供API;
3)JNI: 使Application Framework和Libraries可交互;
4)Libraries: 包括Camera Framework和Camera Service(camera service和camera client);
5)HAL: 硬件抽象层, 用来链接driver和 Camera Service;
6)Kernel: image sensor driver的实作.
API2流程

解释下来 其实就是CameraManager调用openCamera 下发open指令到底层,然后在CameraDevice.StateCallback 的onOpened 中 拿到底层返回来的CameraDevice 通过CameraDevice去创建Session(createCaptureSession) 创建Session是需要添加对应的surface 预览的surface 有TextureView SurfaceView 拍照对应的Surface 为ImageReader 然后在Session的状态回调的onConfigured里(CameraCaptureSession.StateCallback)拿到CameraCaptureSession对象 通过CameraCaptureSession 可以申请预览 setRepeatingRequest 或者是 拍照 Capture 录制视频 需要配置 MediaRecorder
废话不多说 实战开始
首先利用AndroidStudio 创建工程


Next
Finish
打开布局文件

一步步实现 就慢慢来,activity_main.xml布局文件代码
<?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"
android:background="#000000"
>
<TextureView
android:id="@+id/previewSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/takePictureButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="70dp"
android:background="@drawable/shape_white_ring"
android:src="@drawable/shape_take_photo" />
</RelativeLayout>
看到我放了两个控件一个TextureView 用来预览的控件 SurfaceView 等等也可以 还有一个拍照按钮 ImageButton
首先我们实现预览 预览是一切的基础
package com.example.camera2demo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//预览画面控件
private TextureView mTextureView;
//拍照按钮
private ImageButton mTakePictureButton;
//日志的tag
private final String TAG = "Camera2Demo" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化预览控件
mTextureView = findViewById(R.id.previewSurfaceView);
//初始化拍照按钮
mTakePictureButton = findViewById(R.id.takePictureButton);
}
@Override
protected void onStart() {
super.onStart();
//设置拍照按钮监听
mTakePictureButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.takePictureButton:
takePicture();
break;
}
}
private void takePicture() {
Log.d(TAG,"-----takePicture");
}
}
可以看到 就是在onCreate 里去 初始化 预览控件和拍照按钮 并在 onStart 里去设置按钮 监听
在点击时间里 写了空函数 takePicture 基本框架已经实现
要实现预览 我们是需要 申请Camera权限的 所以接下来我们先申请权限
Android 6.0 之前只需要在 AndroidManifest.xml 中加入 就OK 6.0 之后需要加入并且动态申请权限
<uses-permission android:name="android.permission.CAMERA" />

代码 块如下
package com.example.camera2demo;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//预览画面控件
private TextureView mTextureView;
//拍照按钮
private ImageButton mTakePictureButton;
//日志的tag
private final String TAG = "Camera2Demo";
//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
private final String[] REQUIRED_PERMISSIONS = new String[]{
"android.permission.CAMERA",
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化预览控件
mTextureView = findViewById(R.id.previewSurfaceView);
//初始化拍照按钮
mTakePictureButton = findViewById(R.id.takePictureButton);
}
@Override
protected void onResume() {
super.onResume();
//检查权限申请权限
if (allPermissionsGranted()) {
Log.d(TAG, "权限已授予");
} else {
Log.d(TAG, "申请权限");
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
@Override
protected void onStart() {
super.onStart();
//设置拍照按钮监听
mTakePictureButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.takePictureButton:
takePicture();
break;
}
}
private boolean allPermissionsGranted() {
Log.d(TAG, "----- 检查权限");
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//权限回调 申请完权限之后 返回的结果在这里接收
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//这个1 是申请权限时的第三个参数
if (requestCode == 1) {
if (allPermissionsGranted()) {
openCamera();
} else {
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
}
private void openCamera() {
Log.d(TAG,"----------openCamera");
}
private void takePicture() {
Log.d(TAG,"-----takePicture");
}
}
在刚刚 的基础之上加了三个函数 一个检查权限的函数 一个权限回调函数 一个 openCamera的空函数 权限获取之后我们就正式进入openCamera
package com.example.camera2demo;
import static java.lang.String.valueOf;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//预览画面控件
private TextureView mTextureView;
//拍照按钮
private ImageButton mTakePictureButton;
//日志的tag
private final String TAG = "Camera2Demo";
//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
private final String[] REQUIRED_PERMISSIONS = new String[]{
"android.permission.CAMERA",
};
//处理回调函数的线程
private HandlerThread mCameraThread;
private Handler mCameraHandler;
//具体的相机设备
private CameraDevice mCameraDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化预览控件
mTextureView = findViewById(R.id.previewSurfaceView);
//初始化拍照按钮
mTakePictureButton = findViewById(R.id.takePictureButton);
//开启线程 用于处理回调函数 可开可不开
startCameraThread();
}
@Override
protected void onResume() {
super.onResume();
//检查权限申请权限
if (allPermissionsGranted()) {
Log.d(TAG, "权限已授予");
} else {
Log.d(TAG, "申请权限");
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
@Override
protected void onStart() {
super.onStart();
//设置拍照按钮监听
mTakePictureButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.takePictureButton:
takePicture();
break;
}
}
private boolean allPermissionsGranted() {
Log.d(TAG, "----- 检查权限");
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//权限回调 申请完权限之后 返回的结果在这里接收
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//这个1 是申请权限时的第三个参数
if (requestCode == 1) {
if (allPermissionsGranted()) {
openCamera();
} else {
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
}
//开启线程 用于处理回调函数 可开可不开
private void startCameraThread() {
mCameraThread = new HandlerThread("CameraThread");
mCameraThread.start();
mCameraHandler = new Handler(mCameraThread.getLooper());
}
@SuppressLint("MissingPermission")
private void openCamera() {
Log.d(TAG, "----------openCamera");
// 1.获取CameraManager 需要注意的是这里需要强转
CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
// 2.通过CameraManager 获取相机ID
try {
String cameraId[] = cameraManager.getCameraIdList();
for (String s : cameraId) {
Log.d(TAG, "--------- cameraId = " + s);
}
// 3. 获取 0 后置的相机信息
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
// 例如获取分辨率的信息 通过对应的KEY 获取对应信息
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
for (Size size : sizes) {
Log.d(TAG,"size = "+size);
}
// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调
// 第三个是处理回调的线程 这里需要检查权限
cameraManager.openCamera(valueOf(0),cameraDeviceStateCallback,mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
}
};
private void takePicture() {
Log.d(TAG, "-----takePicture");
}
}
这段代码 开了个线程来处理回调函数可以要可不要,如果选择在主线程执行就可以穿null,加入了 openCamera 的关键 步骤获取CameraManger 通过CameraManger 可以获取到相机ID 如下图
还可以获取相机的特征信息 比如支持的分辨率 如下图
最核心的是可以下发openCamera 指令
然后在回调里拿到 CameraDevice 拿到CameraDevice 就可以创建Session 接下来是创建Session 建立预览
package com.example.camera2demo;
import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO;
import static java.lang.String.valueOf;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.annotation.SuppressLint;
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.params.StreamConfigurationMap;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//预览画面控件
private TextureView mTextureView;
//拍照按钮
private ImageButton mTakePictureButton;
//日志的tag
private final String TAG = "Camera2Demo";
//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
private final String[] REQUIRED_PERMISSIONS = new String[]{
"android.permission.CAMERA",
};
//处理回调函数的线程
private HandlerThread mCameraThread;
private Handler mCameraHandler;
//具体的相机设备
private CameraDevice mCameraDevice;
//创建Session的构建者
private CaptureRequest.Builder mCaptureRequestBuilder;
//CameraCaptureSession是创建预览拍照 录像请求的关键 可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果
private CameraCaptureSession mCameraCaptureSession;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化预览控件
mTextureView = findViewById(R.id.previewSurfaceView);
//设置预览控件的监听 防止surface 没有渲染好就打开相机了
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
//初始化拍照按钮
mTakePictureButton = findViewById(R.id.takePictureButton);
//开启线程 用于处理回调函数 可开可不开
startCameraThread();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onStart() {
super.onStart();
//设置拍照按钮监听
mTakePictureButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.takePictureButton:
takePicture();
break;
}
}
private boolean allPermissionsGranted() {
Log.d(TAG, "----- 检查权限");
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//权限回调 申请完权限之后 返回的结果在这里接收
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//这个1 是申请权限时的第三个参数
if (requestCode == 1) {
if (allPermissionsGranted()) {
openCamera();
} else {
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
}
//开启线程 用于处理回调函数 可开可不开
private void startCameraThread() {
mCameraThread = new HandlerThread("CameraThread");
mCameraThread.start();
mCameraHandler = new Handler(mCameraThread.getLooper());
}
@SuppressLint("MissingPermission")
private void openCamera() {
Log.d(TAG, "----------openCamera");
// 1.获取CameraManager 需要注意的是这里需要强转
CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
// 2.通过CameraManager 获取相机ID
try {
String cameraId[] = cameraManager.getCameraIdList();
for (String s : cameraId) {
Log.d(TAG, "--------- cameraId = " + s);
}
// 3. 获取 0 后置的相机信息
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
// 例如获取分辨率的信息 通过对应的KEY 获取对应信息
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
for (Size size : sizes) {
Log.d(TAG, "size = " + size);
}
// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调
// 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行 这里需要检查权限
cameraManager.openCamera(valueOf(0), cameraDeviceStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
createPreView();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
}
};
private void createPreView() {
//创建预览 之前创建Session 创建Session 需要接收数据的Surface
//下面就是获取Surface
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
//设置SurfaceView的缓冲区分辨率 与手机用的分辨一致 就可以防止预览拉伸
surfaceTexture.setDefaultBufferSize(4160, 1856);
Surface previewSurface = new Surface(surfaceTexture);
//如果是surfaceView 如下
// SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
// surfaceHolder.setFixedSize(1920,1080);
// surface = surfaceHolder.getSurface();
try {
//创建Session的构建者
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//将Surface 传递下去
mCaptureRequestBuilder.addTarget(previewSurface);
//通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//创建Session
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), cameraCaptureSessionStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
try {
session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
};
//很多人写到这里觉得大功告成了 是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入 崩溃了 还找不到原因 这里是因为 我们openCamera时Surface
//还么渲染好 导致的解决办法有两个
//1
TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
//检查权限申请权限
if (allPermissionsGranted()) {
Log.d(TAG, "权限已授予");
openCamera();
} else {
Log.d(TAG, "申请权限");
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
@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) {
}
};
//2.在onCreate里添加下面这段
// mTextureView.post(new Runnable() {
// @Override
// public void run() {
// if (allPermissionsGranted()) {
// Log.d(TAG,"权限ok");
// startCamera();
// } else {
// Log.d(TAG,"申请权限");
// ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
// }
// }
// });
private void takePicture() {
Log.d(TAG, "-----takePicture");
}
}
至此简单的预览完成了
预览拍照代码
package com.example.camera2demo;
import static java.lang.String.valueOf;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//预览画面控件
private TextureView mTextureView;
//拍照按钮
private ImageButton mTakePictureButton;
//日志的tag
private final String TAG = "Camera2Demo";
//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
private final String[] REQUIRED_PERMISSIONS = new String[]{
"android.permission.CAMERA",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE",
};
//处理回调函数的线程
private HandlerThread mCameraThread;
private Handler mCameraHandler;
//具体的相机设备
private CameraDevice mCameraDevice;
//创建Session的构建者
private CaptureRequest.Builder mCaptureRequestBuilder;
//CameraCaptureSession是创建预览拍照 录像请求的关键 可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果
private CameraCaptureSession mCameraCaptureSession;
private ImageReader mImageReader = null;
protected ImageView mThumbnail;
//用于处理拍照方向问题
protected static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
//90 270 还没处理
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);
//初始化预览控件
mTextureView = findViewById(R.id.previewSurfaceView);
//设置预览控件的监听 防止surface 没有渲染好就打开相机了
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
//初始化拍照按钮
mTakePictureButton = findViewById(R.id.takePictureButton);
mThumbnail = findViewById(R.id.thumbnail);
mThumbnail.setOnClickListener(this);
//开启线程 用于处理回调函数 可开可不开
startCameraThread();
//去掉导航栏
getSupportActionBar().hide();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//透明导航栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onStart() {
super.onStart();
//设置拍照按钮监听
mTakePictureButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.takePictureButton:
takePicture();
break;
case R.id.thumbnail:
gotoGallery();
break;
}
}
private boolean allPermissionsGranted() {
Log.d(TAG, "----- 检查权限");
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//权限回调 申请完权限之后 返回的结果在这里接收
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//这个1 是申请权限时的第三个参数
if (requestCode == 1) {
if (allPermissionsGranted()) {
openCamera();
} else {
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
}
//开启线程 用于处理回调函数 可开可不开
private void startCameraThread() {
mCameraThread = new HandlerThread("CameraThread");
mCameraThread.start();
mCameraHandler = new Handler(mCameraThread.getLooper());
}
@SuppressLint("MissingPermission")
private void openCamera() {
Log.d(TAG, "----------openCamera");
// 1.获取CameraManager 需要注意的是这里需要强转
CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
// 2.通过CameraManager 获取相机ID
try {
String cameraId[] = cameraManager.getCameraIdList();
for (String s : cameraId) {
Log.d(TAG, "--------- cameraId = " + s);
}
// 3. 获取 0 后置的相机信息
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
// 例如获取分辨率的信息 通过对应的KEY 获取对应信息
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
for (Size size : sizes) {
Log.d(TAG, "size = " + size);
}
// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调
// 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行 这里需要检查权限
cameraManager.openCamera(valueOf(0), cameraDeviceStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
createPreView();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
}
};
private void createPreView() {
initImageReader(); //拍照要初始化
//创建预览 之前创建Session 创建Session 需要接收数据的Surface
//下面就是获取Surface
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
//设置SurfaceView的缓冲区分辨率 与手机用的分辨一致 就可以防止预览拉伸
surfaceTexture.setDefaultBufferSize(1920, 1080);
Surface previewSurface = new Surface(surfaceTexture);
//如果是surfaceView 如下
// SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
// surfaceHolder.setFixedSize(1920,1080);
// surface = surfaceHolder.getSurface();
try {
//创建Session的构建者
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//将Surface 传递下去
mCaptureRequestBuilder.addTarget(previewSurface);
//通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//创建Session
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), cameraCaptureSessionStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
try {
session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
};
//很多人写到这里觉得大功告成了 是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入 崩溃了 还找不到原因 这里是因为 我们openCamera时Surface
//还么渲染好 导致的解决办法有两个
//1
TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
//检查权限申请权限
if (allPermissionsGranted()) {
Log.d(TAG, "权限已授予");
openCamera();
} else {
Log.d(TAG, "申请权限");
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
showThumbnail();
}
@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) {
}
};
//2.在onCreate里添加下面这段
// mTextureView.post(new Runnable() {
// @Override
// public void run() {
// if (allPermissionsGranted()) {
// Log.d(TAG,"权限ok");
// startCamera();
// } else {
// Log.d(TAG,"申请权限");
// ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
// }
// }
// });
//拍照 开始拍照之前得配置对应的Surfac 就是ImageReader
protected void initImageReader() {
Log.d("djh", "--------------------initImageReader");
//四个参数分别是照片的分辨率 格式 最多获取几帧
mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);
//设置ImageReader监听,当有图像流数据可用时会回调onImageAvailable
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
Toast.makeText(MainActivity.this, "图片已保存", Toast.LENGTH_SHORT).show();
//获得Image 数据
Image image = imageReader.acquireNextImage();
//可以开启线程保存图片
new Thread(new Runnable() {
@Override
public void run() {
//字节缓冲
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String path = Environment.getExternalStorageDirectory() + "/DCIM/camera/myPicture"
+ System.currentTimeMillis() + ".jpg";
File imageFile = new File(path);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(imageFile);
fos.write(data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
broadcast();
image.close(); // 必须关闭 不然拍第二章会报错
showThumbnail();
}
}
}).start();
}
}, null);
}
private void takePicture() {
Log.d(TAG, "-----takePicture");
try {
//拍照跟预览的区别就是Builder不一样回调不一样仅此而已
CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
int rotation = getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
mCameraCaptureSession.stopRepeating();
mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
try {
session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
};
// 通知广播刷新相册
protected void broadcast() {
Log.d("djh", "--------------------broadcast");
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(path));
intent.setData(uri);
MainActivity.this.sendBroadcast(intent);
}
//预览拍照完成显示缩略图
protected void showThumbnail() {
ArrayList<String> imageList = getImageFilePath();
String path = imageList.get(imageList.size() - 1);
if (path.contains("jpg")) {
Bitmap bitmap = (Bitmap) BitmapFactory.decodeFile(path);
//又是bitmap 为空 忘记给mImageView finViewById
mThumbnail.setImageBitmap(bitmap);
int rotation = getWindowManager().getDefaultDisplay().getRotation();
mThumbnail.setRotation(ORIENTATION.get(rotation));
} else {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
//取第一帧
Bitmap bitmap = retriever.getFrameAtTime(1);
mThumbnail.setImageBitmap(bitmap);
}
}
//遍历系统相册
protected ArrayList<String> getImageFilePath() {
ArrayList<String> imageList = new ArrayList<>();
File file = new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
File[] dirEpub = file.listFiles();
if (dirEpub.length != 0) {
for (int i = 0; i < dirEpub.length; i++) {
String fileName = dirEpub[i].toString();
imageList.add(fileName);
Log.i("File", "File name = " + fileName);
}
}
return imageList;
}
//跳转相册 (画廊)
protected void gotoGallery() {
ArrayList<String> temp = getImageFilePath();
String lastPath = temp.get(temp.size() - 1);
Uri uri = getMediaUriFromPath(this, lastPath);
Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
intent.setData(uri);
startActivity(intent);
}
@SuppressLint("Range")
public Uri getMediaUriFromPath(Context context, String path) {
Uri uri = null;
if (path.contains("jpg")) {
Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(picUri,
null,
MediaStore.Images.Media.DISPLAY_NAME + "= ?",
new String[]{path.substring(path.lastIndexOf("/") + 1)},
null);
if (cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(picUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
}
cursor.close();
} else if (path.contains("mp4")) {
Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(mediaUri,
null,
MediaStore.Video.Media.DISPLAY_NAME + "= ?",
new String[]{path.substring(path.lastIndexOf("/") + 1)},
null);
if (cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(mediaUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));
}
cursor.close();
}
return uri;
}
}

activity_main.xml
<?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"
android:background="#000000"
>
<TextureView
android:id="@+id/previewSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/takePictureButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="70dp"
android:background="@drawable/shape_white_ring"
android:src="@drawable/shape_take_photo" />
<ImageView
android:id="@+id/thumbnail"
android:layout_width="45dp"
android:layout_height="55dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="50dp"
android:layout_marginBottom="70dp" />
<ImageButton
android:id="@+id/changeCamera"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="50dp"
android:layout_marginBottom="77dp"
android:background="@drawable/camera_quick_switch" />
</RelativeLayout>
再下来就是录像了
录像跟拍照很相似 只是 CaptureRequest.Builder对象不一样 需要配置一个MediaRecorder
预览拍照录制视频的完整代码
package com.example.camera2demo;
import static java.lang.String.valueOf;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//预览画面控件
private TextureView mTextureView;
//拍照按钮
private ImageButton mTakePictureButton;
//日志的tag
private final String TAG = "Camera2Demo";
//权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
private final String[] REQUIRED_PERMISSIONS = new String[]{
"android.permission.CAMERA",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.RECORD_AUDIO",
};
//处理回调函数的线程
private HandlerThread mCameraThread;
private Handler mCameraHandler;
//具体的相机设备
private CameraDevice mCameraDevice;
//创建Session的构建者
private CaptureRequest.Builder mPreviewBuilder;
//CameraCaptureSession是创建预览拍照 录像请求的关键 可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果
private CameraCaptureSession mCameraCaptureSession;
private ImageReader mImageReader = null;
protected ImageView mThumbnail;
//用于处理拍照方向问题
protected static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
//90 270 还没处理
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
private Button mPhotoMode;
private Button mVideoMode;
//拍照按钮
private ImageButton mCapTureVideoButton;
private MediaRecorder mMediaRecorder;
private boolean bStop = false;
private ImageButton mChangeCamera;
private String mCameraId = "0";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化预览控件
mTextureView = findViewById(R.id.previewSurfaceView);
//设置预览控件的监听 防止surface 没有渲染好就打开相机了
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
//初始化拍照按钮
mTakePictureButton = findViewById(R.id.takePictureButton);
mThumbnail = findViewById(R.id.thumbnail);
mThumbnail.setOnClickListener(this);
//开启线程 用于处理回调函数 可开可不开
startCameraThread();
//去掉导航栏
getSupportActionBar().hide();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//透明导航栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
mPhotoMode = findViewById(R.id.photoMode);
mPhotoMode.setOnClickListener(this);
mVideoMode = findViewById(R.id.videoMode);
mVideoMode.setOnClickListener(this);
mCapTureVideoButton = findViewById(R.id.captureVideoButton);
mCapTureVideoButton.setOnClickListener(this);
mChangeCamera = findViewById(R.id.changeCamera);
mChangeCamera.setOnClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onStart() {
super.onStart();
//设置拍照按钮监听
mTakePictureButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.takePictureButton:
takePicture();
break;
case R.id.thumbnail:
gotoGallery();
break;
case R.id.photoMode:
mPhotoMode.setVisibility(View.GONE);
mVideoMode.setVisibility(View.VISIBLE);
mTakePictureButton.setVisibility(View.GONE);
mCapTureVideoButton.setVisibility(View.VISIBLE);
break;
case R.id.videoMode:
mVideoMode.setVisibility(View.GONE);
mPhotoMode.setVisibility(View.VISIBLE);
mTakePictureButton.setVisibility(View.VISIBLE);
mCapTureVideoButton.setVisibility(View.GONE);
break;
case R.id.captureVideoButton:
if (!bStop) {
bStop = true;
captureVideo();
} else {
bStop = false;
stopVideo();
}
break;
case R.id.changeCamera:
changeCameraId();
break;
}
}
private boolean allPermissionsGranted() {
Log.d(TAG, "----- 检查权限");
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//权限回调 申请完权限之后 返回的结果在这里接收
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//这个1 是申请权限时的第三个参数
if (requestCode == 1) {
if (allPermissionsGranted()) {
openCamera();
} else {
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
}
}
//开启线程 用于处理回调函数 可开可不开
private void startCameraThread() {
mCameraThread = new HandlerThread("CameraThread");
mCameraThread.start();
mCameraHandler = new Handler(mCameraThread.getLooper());
}
@SuppressLint("MissingPermission")
private void openCamera() {
Log.d(TAG, "----------openCamera");
// 1.获取CameraManager 需要注意的是这里需要强转
CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
// 2.通过CameraManager 获取相机ID
try {
String cameraId[] = cameraManager.getCameraIdList();
for (String s : cameraId) {
Log.d(TAG, "--------- cameraId = " + s);
}
// 3. 获取 0 后置的相机信息
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
// 例如获取分辨率的信息 通过对应的KEY 获取对应信息
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
for (Size size : sizes) {
Log.d(TAG, "size = " + size);
}
// 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
// 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID 第二个是CameraDevice.StateCallback回调
// 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行 这里需要检查权限
cameraManager.openCamera(mCameraId, cameraDeviceStateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
createPreView();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
}
};
private void createPreView() {
initImageReader(); //拍照要初始化
configMediaRecorder();//录制视频需要初始化
//创建预览 之前创建Session 创建Session 需要接收数据的Surface
//下面就是获取Surface
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
//设置SurfaceView的缓冲区分辨率 与手机用的分辨一致 就可以防止预览拉伸
surfaceTexture.setDefaultBufferSize(1920, 1080);
Surface previewSurface = new Surface(surfaceTexture);
//如果是surfaceView 如下
// SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
// surfaceHolder.setFixedSize(1920,1080);
// surface = surfaceHolder.getSurface();
try {
//创建Session的构建者
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//将Surface 传递下去
mPreviewBuilder.addTarget(previewSurface);
mPreviewBuilder.addTarget(mMediaRecorder.getSurface());
//通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//创建Session
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface(), mMediaRecorder.getSurface()), cameraCaptureSessionStateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
try {
session.setRepeatingRequest(mPreviewBuilder.build(), null, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
};
//很多人写到这里觉得大功告成了 是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入 崩溃了 还找不到原因 这里是因为 我们openCamera时Surface
//还么渲染好 导致的解决办法有两个
//1
TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
//检查权限申请权限
if (allPermissionsGranted()) {
Log.d(TAG, "权限已授予");
openCamera();
} else {
Log.d(TAG, "申请权限");
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
}
//showThumbnail();
}
@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) {
}
};
//2.在onCreate里添加下面这段
// mTextureView.post(new Runnable() {
// @Override
// public void run() {
// if (allPermissionsGranted()) {
// Log.d(TAG,"权限ok");
// startCamera();
// } else {
// Log.d(TAG,"申请权限");
// ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
// }
// }
// });
//拍照 开始拍照之前得配置对应的Surfac 就是ImageReader
protected void initImageReader() {
Log.d("djh", "--------------------initImageReader");
//四个参数分别是照片的分辨率 格式 最多获取几帧
mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);
//设置ImageReader监听,当有图像流数据可用时会回调onImageAvailable
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
Toast.makeText(MainActivity.this, "图片已保存", Toast.LENGTH_SHORT).show();
//获得Image 数据
Image image = imageReader.acquireNextImage();
//可以开启线程保存图片
new Thread(new Runnable() {
@Override
public void run() {
//字节缓冲
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String path = Environment.getExternalStorageDirectory() + "/DCIM/camera/myPicture"
+ System.currentTimeMillis() + ".jpg";
File imageFile = new File(path);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(imageFile);
fos.write(data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
broadcast();
image.close(); // 必须关闭 不然拍第二章会报错
//showThumbnail();
}
}
}).start();
}
}, null);
}
private void takePicture() {
Log.d(TAG, "-----takePicture");
try {
//拍照跟预览的区别就是Builder不一样回调不一样仅此而已
CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
int rotation = getWindowManager().getDefaultDisplay().getRotation();
if (mCameraId.equals("1")) {
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation) + 180);
} else {
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
}
mCameraCaptureSession.stopRepeating();
mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
try {
session.setRepeatingRequest(mPreviewBuilder.build(), null, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
};
// 通知广播刷新相册
protected void broadcast() {
Log.d("djh", "--------------------broadcast");
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(path));
intent.setData(uri);
MainActivity.this.sendBroadcast(intent);
}
//预览拍照完成显示缩略图
protected void showThumbnail() {
ArrayList<String> imageList = getImageFilePath();
String path = imageList.get(imageList.size() - 1);
if (path.contains("jpg")) {
Bitmap bitmap = BitmapFactory.decodeFile(path);
mThumbnail.setImageBitmap(bitmap);
int rotation = getWindowManager().getDefaultDisplay().getRotation();
mThumbnail.setRotation(ORIENTATION.get(rotation));
} else if (path.contains("mp4")) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
//获取第1帧
Bitmap bitmap = retriever.getFrameAtTime(1);
mThumbnail.setImageBitmap(bitmap);
}
}
//遍历系统相册
protected ArrayList<String> getImageFilePath() {
ArrayList<String> imageList = new ArrayList<>();
File file = new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
File[] dirEpub = file.listFiles();
if (dirEpub.length != 0) {
for (int i = 0; i < dirEpub.length; i++) {
String fileName = dirEpub[i].toString();
imageList.add(fileName);
Log.i("File", "File name = " + fileName);
}
}
return imageList;
}
//跳转相册 (画廊)
protected void gotoGallery() {
ArrayList<String> temp = getImageFilePath();
String lastPath = temp.get(temp.size() - 1);
Uri uri = getMediaUriFromPath(this, lastPath);
Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
intent.setData(uri);
startActivity(intent);
}
@SuppressLint("Range")
public Uri getMediaUriFromPath(Context context, String path) {
Uri uri = null;
if (path.contains("jpg")) {
Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(picUri,
null,
MediaStore.Images.Media.DISPLAY_NAME + "= ?",
new String[]{path.substring(path.lastIndexOf("/") + 1)},
null);
if (cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(picUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
}
cursor.close();
} else if (path.contains("mp4")) {
Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(mediaUri,
null,
MediaStore.Video.Media.DISPLAY_NAME + "= ?",
new String[]{path.substring(path.lastIndexOf("/") + 1)},
null);
if (cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(mediaUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));
}
cursor.close();
}
return uri;
}
/**
* 配置录制视频相关数据
*/
private void configMediaRecorder() {
File file = new File(Environment.getExternalStorageDirectory() +
"/DCIM/camera/myMp4" + System.currentTimeMillis() + ".mp4");
if (file.exists()) {
file.delete();
}
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
//如果是前置
if (mCameraId.equals("1")) {
mMediaRecorder.setOrientationHint(270);
} else {
mMediaRecorder.setOrientationHint(90);
}
//设置音频来源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置视频来源
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
//设置输出格式
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。
mMediaRecorder.setVideoEncodingBitRate(8 * 1080 * 1920);
//设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。
mMediaRecorder.setVideoFrameRate(30);
//Size size = getMatchingSize();
mMediaRecorder.setVideoSize(1920, 1080);
Surface surface = new Surface(mTextureView.getSurfaceTexture());
mMediaRecorder.setPreviewDisplay(surface);
mMediaRecorder.setOutputFile(file.getAbsolutePath());
try {
mMediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void captureVideo() {
Log.d(TAG, "--------- captureVideo");
mMediaRecorder.start();
}
private void stopVideo() {
Log.d(TAG, "--------- stopVideo");
mMediaRecorder.stop();
broadcast();
createPreView();
}
private void changeCameraId() {
Log.d(TAG, "changeCamera: success");
if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
Toast.makeText(this, "前置转后置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
} else {
Toast.makeText(this, "后置转前置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
}
mCameraDevice.close();
openCamera();
}
}
xml
<?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"
android:background="#000000">
<TextureView
android:id="@+id/previewSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/takePictureButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="70dp"
android:visibility="visible"
android:background="@drawable/shape_white_ring"
android:src="@drawable/shape_take_photo" />
<ImageButton
android:id="@+id/captureVideoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alagnParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="70dp"
android:visibility="gone"
android:background="@drawable/shape_white_ring"
android:src="@drawable/shape_take_video" />
<ImageView
android:id="@+id/thumbnail"
android:layout_width="45dp"
android:layout_height="55dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="50dp"
android:layout_marginBottom="70dp" />
<ImageButton
android:id="@+id/changeCamera"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="50dp"
android:layout_marginBottom="77dp"
android:background="@drawable/camera_quick_switch" />
<Button
android:id="@+id/photoMode"
android:layout_width="85dp"
android:layout_height="40dp"
android:text="Photo"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:visibility="visible"
android:layout_marginBottom="160dp"/>
<Button
android:id="@+id/videoMode"
android:layout_width="85dp"
android:text="Video"
android:layout_height="40dp"
android:visibility="gone"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="160dp"
/>
</RelativeLayout>
CameraX
用CameraX 时需要注意的是 build.gradle里面需要添加

//CameraX
def camerax_version = "1.0.0-alpha02"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
CameraX api
CameraX 可以理解成将Camera2的API 进行了封装 使用变得更加简洁
实现预览 获取Camera权限之后 通过PreviewConfig去设置分辨率,画面比例,CameraId等等
再 new 一个 Preview 对象时将PreviewConfig对象传递下去,再利用bindTolifecycle 把Preview 对象 传递下去就完成预览了,相比Camera2是不是要简单很多呢
拍照也很简单 只需要用ImageCaptureConfig 配置拍照信息 比例 CameraId 拍照方式等等
再去new一个ImageCapture对象 将ImageCaptureConfig传递下去 再给 bindTolifecycle 多一个参数 这个参数传递ImageCapture对象
录像 VideoCaptureConfig
Quality.UHD,适用于 4K 超高清视频大小 (2160p)Quality.FHD,适用于全高清视频大小 (1080p)Quality.HD,适用于高清视频大小 (720p)Quality.SD,适用于标清视频大小 (480p)

本文将详细讲解如何使用Camera2和CameraX库在Android应用中实现相机预览、拍照及录制视频。涵盖Camera2的相机方向、层级架构、API流程,以及CameraX的简洁API介绍。并提供了基础的代码实现。
1216





