职场小白迷上优美句子:
还是电影 《无问西东》中的台词,这句有点感人:
沈光耀的妈妈对沈光耀说:"当初你离家千里,来到这个地方读书,你父亲和我都没有反对过,因为,是我们想你,能享受到人生的乐趣,比如读万卷书行万里路,比如同你喜欢的女孩子结婚生子。注意不是给我增添子孙,而是你自己,能够享受为人父母的乐趣,你一生所要追求的功名利禄,没有什么是你的祖上没经历过的,那些只不过是人生的幻光。我怕,你还没想好怎么过这一生,你的命就没了啊!"
推荐两篇对Camera2的讲解非常详细的博客
新根 - - - Android Camera2 API和拍照与录像过程
Camera2.0新API下的摄像头预览、原始图像数据获取等_黄政的博客-CSDN博客
效果图:
源码地址:
github开源相机Camera2源码https://github.com/13767004362/Camera2App.git
拍照操作类代码:
public class PictureOperater extends BaseCamera2Operator {
private static final String TAG = PictureOperater.class.getSimpleName();
private WorkThreadUtils workThreadManager;
/**
* 相机最大的预览宽度
*/
private static final int MAX_PREVIEW_WIDTH = 1920;
/**
* 相机最大的预览高度
*/
private static final int MAX_PREVIEW_HEIGHT = 1080;
/**
* 处理静态图片的输出
*/
private ImageReader imageReader;
/**
* 相机传感器方向
*/
private int mSensorOrientation;
/**
* 相机预览的大小Size
*/
private Size mPreviewSize;
/**
* 是否支持自动对焦
*/
private boolean mAutoFocusSupported;
/**
* 是否支持闪光灯
*/
private boolean mFlashSupported;
/**
* 相机的Id
*/
private String mCameraId;
/**
* 预览请求的Builder
*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/**
* 预览的请求
*/
private CaptureRequest mPreviewRequest;
private CameraCaptureSession mCaptureSession;
/**
* 开启相机的锁住时间
*/
private final int LOCK_TIME = 2500;
private Camera2Manager camera2Manager;
/**
* 最小的焦距
*/
private float minimalFocalDistance = 0;
/**
* 最大的数字变焦值,缩放值
*/
private float maxZoom = 0;
public PictureOperater(Camera2Manager camera2Manager) {
this.camera2Manager = camera2Manager;
this.workThreadManager = camera2Manager.getWorkThreadManager();
}
@Override
public void startOperate() {
TextureView textureView = getTextureView();
if (textureView.isAvailable()) {
openCamera(getTextureViewContext(), textureView.getWidth(), textureView.getHeight());
} else {
textureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
public void stopOperate() {
Log.i(TAG, TAG+" 关闭相机的操作 ");
closeCamera();
}
@Override
public void openCamera(Activity activity, int width, int height) {
if (PermissionsManager.checkCameraPermission(activity)) {
setUpCameraOutputs(activity, width, height);
configureTransform(activity, width, height);
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
//打开相机需要一定时间,因锁住2.5秒,防止程序退出关闭相机
if (!mCameraOpenCloseLock.tryAcquire(LOCK_TIME, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
manager.openCamera(mCameraId, stateCallback, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
}
@Override
public void closePreviewSession() {
if (mCaptureSession != null) {
mCaptureSession.close();
mCaptureSession = null;
}
}
@Override
public void configureTransform(Activity activity, int viewWidth, int viewHeight) {
if (null == getTextureView() || null == mPreviewSize || null == activity) {
return;
}
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.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 / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.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);
}
getTextureView().setTransform(matrix);
}
@Override
public void writePictureData(Image image) {
if (camera2ResultCallBack != null) {
camera2ResultCallBack.callBack(ObservableBuilder.createWriteCaptureImage(appContext, image));
}
}
@Override
public void startPreView() {
//开启相机预览界面
createCameraPreviewSession();
}
@Override
public void cameraClick() {
takePicture();
}
/**
* 关闭当前的相机设备,释放资源
*/
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
closePreviewSession();
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != imageReader) {
imageReader.close();
imageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closi" + "ng.", e);
} finally {
mCameraOpenCloseLock.release();
}
}
/**
* 拍照
*/
public void takePicture() {
ToastUtils.showToast(camera2Manager.getContext()," 客官,请别抖动,正在拍照中。");
if (mAutoFocusSupported) {
Log.i(TAG,TAG+"支持自动调焦,正在锁住焦点");
lockFocus();
} else {//设备不支持自动对焦,则直接拍照。
Log.i(TAG,TAG+"不支持自动调焦,直接拍照");
captureStillPicture();
}
}
/**
* 拍照的第一步,锁住焦点。
*/
private void lockFocus() {
try {
//告诉相机,这里已经锁住焦点
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
// 标识,正在进行拍照动作
mState = STATE_WAITING_LOCK;
//进行拍照处理
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 相机开始预览,创建一个CameraCaptureSession对象
*/
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = getTextureView().getSurfaceTexture();
//assert断言表达式,为true继续。
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// 创建一个预览界面需要用到的Surface对象
Surface surface = new Surface(texture);
// 将CaptureRequest的构建器与Surface对象绑定在一起
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// 为相机预览,创建一个CameraCaptureSession对象
mCameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// 相机已经关闭
if (null == mCameraDevice) {
return;
}
//当cameraCaptureSession已经准备完成,开始显示预览界面
mCaptureSession = cameraCaptureSession;
setCameraCaptureSession();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
//配置失败
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 设置CameraCaptureSession的特征:
* <p>
* 自动对焦,闪光灯
*/
private void setCameraCaptureSession() {
try {
setFocus(mPreviewRequestBuilder);
//若是需要则开启,闪光灯
setAutoFlash(mPreviewRequestBuilder);
// 最后,开启相机预览界面的显示
mPreviewRequest = mPreviewRequestBuilder.build();
//为CameraCaptureSession设置复用的CaptureRequest。
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private float currentZoom;
@Override
public void notifyFocusState() {
if (mPreviewRequestBuilder != null) {
try {
currentZoom = maxZoom * camera2Manager.getZoomProportion();
setZoom(currentZoom);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private Rect zoomRect;
private void setZoom(float currentZoom) {
try {
zoomRect=createZoomReact();
if (zoomRect==null){
Log.i(TAG, "相机不支持 zoom " );
return ;
}
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION,zoomRect);
mPreviewRequest = mPreviewRequestBuilder.build();
Log.i(TAG, " 最大缩放值 " + maxZoom + " 设置缩放值 " + currentZoom );
//为CameraCaptureSession设置复用的CaptureRequest。
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 计算出zoom所对应的裁剪区域
* @return
*/
private Rect createZoomReact() {
if (currentZoom==0){
return null;
}
try {
Rect rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (rect==null){
return null;
}
zoomRect =Camera2Utils.createZoomRect(rect,currentZoom);
Log.i(TAG, "zoom对应的 rect对应的区域 " + zoomRect.left + " " + zoomRect.right + " " + zoomRect.top + " " + zoomRect.bottom);
} catch (Exception e) {
e.printStackTrace();
}
return zoomRect;
}
/**
* -
* 设置调焦方式:自动连续对焦还是手动调焦
*
* @param requestBuilder
*/
private void setFocus(CaptureRequest.Builder requestBuilder) {
/* if (camera2Manager.isManualFocus()) {
float focusDistance = minimum_focus_distance * camera2Manager.getZoomProportion();
setManualFocus(requestBuilder, focusDistance);
} else {
}*/
setAutoFocus(requestBuilder);
}
/**
* 设置连续自动对焦
*
* @param requestBuilder
*/
private void setAutoFocus(CaptureRequest.Builder requestBuilder) {
if (requestBuilder != null) {
//为相机预览设置连续对焦。
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
}
}
/**
* 设置手动对焦,设置焦距值
*
* @param requestBuilder
*/
private void setManualFocus(CaptureRequest.Builder requestBuilder, float distance) {
//若是机器不支持焦距设置,则需要检查。
try {
if (requestBuilder != null) {
//先关闭自动对焦的模式
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_OFF);
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF);
Log.i(TAG, "手动调焦的 " + distance + " 最大范围值是 " + minimum_focus_distance);
//设置焦距值
requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, distance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Camera state: Showing camera preview.
* 相机预览状态
*/
private static final int STATE_PREVIEW = 0;
/**
* Camera state: Waiting for the focus to be locked.
* <p>
* 相机拍照,被锁住,等待焦点状态
*/
private static final int STATE_WAITING_LOCK = 1;
/**
* Camera state: Waiting for the exposure to be precapture state.
*/
private static final int STATE_WAITING_PRECAPTURE = 2;
/**
* Camera state: Waiting for the exposure state to be something other than precapture.
*/
private static final int STATE_WAITING_NON_PRECAPTURE = 3;
/**
* Camera state: Picture was taken.
* 图片已经获取
*/
private static final int STATE_PICTURE_TAKEN = 4;
/**
* The current state of camera state for taking pictures.
*
* @see #mCaptureCallback
*/
private int mState = STATE_PREVIEW;
/**
* CameraCaptureSession.CaptureCallback : 处理捕获到的JPEG事件。
*/
private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
private void process(CaptureResult result) {
switch (mState) {
//正常预览状态
case STATE_PREVIEW: {
break;
}
//刚开始拍照,锁住,等待状态
case STATE_WAITING_LOCK: {
//当前自动对焦的状态
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState == null) {
captureStillPicture();
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
mState = STATE_PICTURE_TAKEN;
captureStillPicture();
} else {
runPrecaptureSequence();
}
}else{
mState = STATE_PICTURE_TAKEN;
captureStillPicture();
}
break;
}
//等待,预捕获
case STATE_WAITING_PRECAPTURE: {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
mState = STATE_WAITING_NON_PRECAPTURE;
}
break;
}
//已经完成预捕获,直接拍照。
case STATE_WAITING_NON_PRECAPTURE: {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
mState = STATE_PICTURE_TAKEN;
captureStillPicture();
}
break;
}
default:
break;
}
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
process(partialResult);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
process(result);
}
};
/**
* <p>
* 运行预捕获的序列,为捕获一个静态图片
*/
private void runPrecaptureSequence() {
try {
// 告诉相机,这里触发.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
//设置成预捕获状态,将需等待。
mState = STATE_WAITING_PRECAPTURE;
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 拍照一个静态的图片
* ,当在CaptureCallback监听器响应的时候调用该方法。
* <p>
* 当数字调焦缩放的时候,在写入图片数中也要设置。
*/
private void captureStillPicture() {
try {
final Activity activity = getTextureViewContext();
if (null == activity || null == mCameraDevice) {
return;
}
// 创建一个拍照的CaptureRequest.Builder
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(imageReader.getSurface());
// 使用相同的AE和AF模式作为预览.
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//让相机中缩放效果和图片保持一致
// zoomRect=createZoomReact();
if (zoomRect != null) {
Log.i(TAG," 拍照 添加裁剪区域 "+zoomRect.toString());
captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);
}
setAutoFlash(captureBuilder);
// 方向
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, Camera2Utils.getOrientation(ORIENTATIONS, mSensorOrientation, rotation));
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session
, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
//拍照完成,进行释放焦点操作。
unlockFocus();
}
};
//先停止以前的预览状态
mCaptureSession.stopRepeating();
mCaptureSession.abortCaptures();
//执行拍照状态
mCaptureSession.capture(captureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 完成一些列拍照后,释放焦点。
*/
private void unlockFocus() {
try {
// 重置一系列的对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
setAutoFlash(mPreviewRequestBuilder);
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, workThreadManager.getBackgroundHandler());
// 恢复正常状态
mState = STATE_PREVIEW;
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, workThreadManager.getBackgroundHandler());
Log.i(TAG, TAG+" 拍照完成,释放焦点 unlockFocus() ");
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 设置是否开启闪光灯
*
* @param requestBuilder
*/
private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
if (mFlashSupported) {
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
}
}
/**
* 获取到最大的焦距值
*/
private float minimum_focus_distance;
/**
* 获取相机参数的类
*/
private CameraCharacteristics cameraCharacteristics;
/**
* 设置Camera的相关参数变量,长、宽,且返回相机的Id.
*
* @param width
* @param height
*/
private void setUpCameraOutputs(Activity activity, int width, int height) {
CameraManager manager = (CameraManager) getTextureViewContext().getSystemService(Context.CAMERA_SERVICE);
try {
//获取到可用的相机
for (String cameraId : manager.getCameraIdList()) {
//获取到每个相机的参数对象,包含前后摄像头,分辨率等
cameraCharacteristics = manager.getCameraCharacteristics(cameraId);
if (!Camera2Utils.matchCameraDirection(cameraCharacteristics, currentDirection)) {
continue;
}
//存储流配置类
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
//检查设备,是否支持自动对焦
mAutoFocusSupported = Camera2Utils.checkAutoFocus(cameraCharacteristics);
//获取最小焦距值。
Float minFocalDistance = Camera2Utils.getMinimumFocusDistance(cameraCharacteristics);
if (minFocalDistance != null) {
minimum_focus_distance = minFocalDistance;
}
Float maxZoomValue = Camera2Utils.getMaxZoom(cameraCharacteristics);
if (maxZoomValue != null) {
maxZoom = maxZoomValue;
}
Log.i(TAG, (currentDirection == CameraCharacteristics.LENS_FACING_BACK ? "后" : "前") + " 摄像头 " + " 是否支持自动对焦 " + mAutoFocusSupported + " 获取到焦距的最大值 " + minimum_focus_distance + " 最大的缩放值 " + maxZoom);
//对于静态图片,使用可用的最大值来拍摄。
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizeByArea());
//设置ImageReader,将大小,图片格式
imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/2);
imageReader.setOnImageAvailableListener(onImageAvailableListener, workThreadManager.getBackgroundHandler());
//获取到屏幕的旋转角度,进一步判断是否,需要交换维度来获取预览大小
int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
//获取相机传感器方向
mSensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
boolean swappedDimensions = false;
switch (displayRotation) {
case Surface.ROTATION_0:
case Surface.ROTATION_180:
if (mSensorOrientation == 90 || mSensorOrientation == 270) {
swappedDimensions = true;
}
break;
case Surface.ROTATION_90:
case Surface.ROTATION_270:
if (mSensorOrientation == 0 || mSensorOrientation == 180) {
swappedDimensions = true;
}
break;
default:
Log.e(TAG, "Display rotation is invalid: " + displayRotation);
}
Point displaySize = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
//当角度反了的时候
if (swappedDimensions) {
rotatedPreviewWidth = height;
rotatedPreviewHeight = width;
maxPreviewWidth = displaySize.y;
maxPreviewHeight = displaySize.x;
}
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
mPreviewSize = Camera2Utils.chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest, new CompareSizeByArea());
// 计算出来的预览大小,设置成TextureView宽高.
int orientation = activity.getResources().getConfiguration().orientation;
AutoFitTextureView mTextureView = (AutoFitTextureView) getTextureView();
//横屏
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
// 检查,相机是否支持闪光。
Boolean available = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
mCameraId = cameraId;
// Log.i(TAG, " 根据相机的前后摄像头" + mCameraId + " 方向是:" + currentDirection);
return;
}
} catch (Exception e) {
e.printStackTrace();
//不支持该设备
if (e instanceof NullPointerException) {
ToastUtils.showToast(appContext, "设备不支持Camera2 API");
}
}
}
}
录像操作类代码:
public class VideoRecordOperator extends BaseCamera2Operator {
public static final String TAG = VideoRecordOperator.class.getSimpleName();
private WorkThreadUtils workThreadManager;
/**
* 视频录制的大小
*/
private Size mVideoSize;
/**
* 相机预览的大小Size
*/
private Size mPreviewSize;
/**
* MediaRecorder
*/
private MediaRecorder mMediaRecorder;
/**
* 当前是否是在录制视频
*/
private boolean mIsRecordingVideo;
/**
* 传感器的方向
*/
private Integer mSensorOrientation;
/**
* 相机预览请求的Builder
*/
private CaptureRequest.Builder mPreviewBuilder;
/**
* 点击开启录制时候创建的新视频文件路径
*/
private String mNextVideoAbsolutePath;
private CompositeSubscription compositeSubscription;
private Camera2Manager camera2Manager;
public VideoRecordOperator(Camera2Manager camera2Manager) {
this.camera2Manager = camera2Manager;
this.workThreadManager = camera2Manager.getWorkThreadManager();
this.oldVideoPath = new CopyOnWriteArrayList<>();
this.compositeSubscription = new CompositeSubscription();
}
@Override
public void writePictureData(Image image) {
}
/**
* 开始相机的预览界面,创建一个预览的session会话。
*/
@Override
public void startPreView() {
TextureView mTextureView = getTextureView();
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
return;
}
//开始相机预览
try {
closePreviewSession();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Surface previewSurface = new Surface(texture);
mPreviewBuilder.addTarget(previewSurface);
//从拍照切换到摄像头,获取录像完成后需要重新恢复以前的状态
float currentZoom = camera2Manager.getZoomProportion() * maxZoom;
updateZoomRect(currentZoom);
mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mPreviewSession = session;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Activity activity = getTextureViewContext();
if (null != activity) {
Toast.makeText(activity, "相机预览配置失败", Toast.LENGTH_SHORT).show();
}
}
}, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
if (null != mTextureView) {
configureTransform(getTextureViewContext(), mTextureView.getWidth(), mTextureView.getHeight());
}
}
/**
* 在 startPreView()之后执行用于更新相机预览界面
*/
private void updatePreview() {
if (null == mCameraDevice) {
return;
}
try {
setUpCaptureRequestBuilder(mPreviewBuilder);
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
}
/**
* 为相机创建一个CameraCaptureSession
*/
private CameraCaptureSession mPreviewSession;
@Override
public void closePreviewSession() {
if (mPreviewSession != null) {
mPreviewSession.close();
mPreviewSession = null;
}
}
@Override
public void cameraClick() {
if (mIsRecordingVideo) {
stopRecordingVideo(true);
} else {
startRecordingVideo();
}
}
/**
* 停止录制
*/
private void stopRecordingVideo(final boolean isFinish) {
// UI
mIsRecordingVideo = false;
/**
* 在MediaRecorder停止前,停止相机预览,防止抛出serious error异常。
*
* android.hardware.camera2.CameraAccessException: The camera device has encountered a serious error
*
* 解决方式:https://stackoverflow.com/questions/27907090/android-camera-2-api
*/
try {
mPreviewSession.stopRepeating();
mPreviewSession.abortCaptures();
} catch (CameraAccessException e) {
e.printStackTrace();
}
Subscription subscription = Observable
//延迟三十毫秒
.timer(30, TimeUnit.MICROSECONDS, Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(l -> {
// 停止录制
mMediaRecorder.stop();
mMediaRecorder.reset();
if (isFinish) {
isRecordGonging = false;
Log.i(TAG, "stopRecordingVideo 录制完成");
if (camera2VideoRecordCallBack != null) {
camera2VideoRecordCallBack.finishRecord();
}
mergeMultipleFileCallBack();
mNextVideoAbsolutePath = null;
this.oldVideoPath.clear();
} else {//暂停的操作
Log.i(TAG, "pauseRecordingVideo 录制暂停");
//若是开始新的录制,原本暂停产生的多个文件合并成一个文件。
this.oldVideoPath.add(mNextVideoAbsolutePath);
if (oldVideoPath.size() > 1) {
mergeMultipleFile();
}
mNextVideoAbsolutePath = null;
}
startPreView();
});
this.compositeSubscription.add(subscription);
}
/**
* 暂停后又从新恢复录制,合并多个视频文件
*/
private void mergeMultipleFile() {
Log.i(TAG, " mergeMultipleFile 开始操作:文件个数 " + this.oldVideoPath.size());
Subscription subscription = ObservableBuilder.createMergeMuiltFile(appContext, this.oldVideoPath.get(0), this.oldVideoPath.get(1))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(filePath -> {
this.oldVideoPath.clear();
this.oldVideoPath.add(filePath);
Log.i(TAG, " mergeMultipleFile 完成: 文件个数" + this.oldVideoPath.size());
});
this.compositeSubscription.add(subscription);
}
/**
* 完成录制,输出最终的视频录制文件
*/
private void mergeMultipleFileCallBack() {
if (this.oldVideoPath.size() > 0) {
Log.i(TAG, " mergeMultipleFileCallBack 开始操作:文件个数 " + this.oldVideoPath.size());
Subscription subscription = ObservableBuilder.createMergeMuiltFile(appContext, this.oldVideoPath.get(0), mNextVideoAbsolutePath)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
if (camera2ResultCallBack != null) {
camera2ResultCallBack.callBack(ObservableBuilder.createVideo(s));
}
Log.i(TAG, " mergeMultipleFileCallBack 完成 且回调");
ToastUtils.showToast(appContext, "视频文件保存在" + s);
});
this.compositeSubscription.add(subscription);
} else {
if (camera2ResultCallBack != null) {
camera2ResultCallBack.callBack(ObservableBuilder.createVideo(mNextVideoAbsolutePath));
}
ToastUtils.showToast(appContext, "视频文件保存在" + mNextVideoAbsolutePath);
}
}
/**
* 暂停录制
*/
public void pauseRecordingVideo() {
stopRecordingVideo(false);
}
/**
* 开始视频录制,创建一个录像的session会话。
*/
private void startRecordingVideo() {
Log.i(TAG, " startRecordingVideo 录制初始化 ");
TextureView mTextureView = getTextureView();
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
return;
}
try {
closePreviewSession();
setUpMediaRecorder();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//创建录制的session会话
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
List<Surface> surfaces = new ArrayList<>();
// 为相机预览设置Surface
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewBuilder.addTarget(previewSurface);
// 为 MediaRecorder设置Surface
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);
//与未录像的状态保持一致。
if (zoomRect != null) {
mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);
}
// Start a capture session
// Once the session starts, we can update the UI and start recording
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mPreviewSession = cameraCaptureSession;
updatePreview();
Log.i(TAG, " startRecordingVideo 正式开始录制 ");
getTextureViewContext().runOnUiThread(() -> {
mIsRecordingVideo = true;
isRecordGonging = true;
mMediaRecorder.start();
if (camera2VideoRecordCallBack != null) {
camera2VideoRecordCallBack.startRecord();
}
});
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Activity activity = getTextureViewContext();
if (null != activity) {
Toast.makeText(activity.getApplicationContext(), "相机设备配置失败", Toast.LENGTH_SHORT).show();
}
}
}, workThreadManager.getBackgroundHandler());
} catch (CameraAccessException | IOException e) {
e.printStackTrace();
}
}
/**
* 设置媒体录制器的配置参数
* <p>
* 音频,视频格式,文件路径,频率,编码格式等等
*
* @throws IOException
*/
private void setUpMediaRecorder() throws IOException {
final Activity activity = getTextureViewContext();
if (null == activity) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mNextVideoAbsolutePath = FileUtils.createVideoDiskFile(appContext, FileUtils.createVideoFileName()).getAbsolutePath();
mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
mMediaRecorder.setVideoEncodingBitRate(10000000);
//每秒30帧
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
switch (mSensorOrientation) {
case SENSOR_ORIENTATION_DEFAULT_DEGREES:
mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
break;
case SENSOR_ORIENTATION_INVERSE_DEGREES:
mMediaRecorder.setOrientationHint(ORIENTATIONS.get(rotation));
break;
default:
break;
}
mMediaRecorder.prepare();
}
private List<String> oldVideoPath;
@Override
public void startOperate() {
TextureView textureView = getTextureView();
if (textureView.isAvailable()) {
openCamera(getTextureViewContext(), textureView.getWidth(), textureView.getHeight());
} else {
textureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
public void stopOperate() {
try {
mCameraOpenCloseLock.acquire();
closePreviewSession();
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mMediaRecorder) {
mMediaRecorder.release();
mMediaRecorder = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.");
} finally {
mCameraOpenCloseLock.release();
}
}
private boolean isRecordGonging = false;
/**
* 是否在进行视频录制,录制状态,包含进行中,暂停中。
*
* @return
*/
public boolean isVideoRecord() {
return isRecordGonging;
}
private Rect zoomRect;
/**
* 更新缩放,数字调焦
*
* @param currentZoom
*/
private void updateZoomRect(float currentZoom) {
try {
Rect rect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (rect == null) {
return;
}
zoomRect = Camera2Utils.createZoomRect(rect, currentZoom);
if (zoomRect == null) {
return;
}
Log.i(TAG, "zoom对应的 rect对应的区域 " + zoomRect.left + " " + zoomRect.right + " " + zoomRect.top + " " + zoomRect.bottom);
mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);
} catch (Exception e) {
e.printStackTrace();
}
}
private float maxZoom;
private CameraCharacteristics characteristics;
@Override
protected void openCamera(Activity activity, int width, int height) {
if (PermissionsManager.checkVideoRecordPermission(getTextureViewContext())) {
if (null == activity || activity.isFinishing()) {
return;
}
AutoFitTextureView textureView = (AutoFitTextureView) getTextureView();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
Log.d(TAG, "tryAcquire");
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("锁住相机开启,超时");
}
for (String cameraId : manager.getCameraIdList()) {
characteristics = manager.getCameraCharacteristics(cameraId);
if (!Camera2Utils.matchCameraDirection(characteristics, currentDirection)) {
continue;
}
//存储流配置类
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
Log.i(TAG, "视频录制,重新配置相机设备" + currentDirection + " " + cameraId);
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
Float maxZoomValue = Camera2Utils.getMaxZoom(characteristics);
if (maxZoomValue != null) {
maxZoom = maxZoomValue;
}
// 计算相机预览和视频录制的的Size
mVideoSize = Camera2Utils.chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
mPreviewSize = Camera2Utils.chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, mVideoSize);
int orientation = activity.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
textureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
textureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
configureTransform(activity, width, height);
mMediaRecorder = new MediaRecorder();
manager.openCamera(cameraId, stateCallback, null);
return;
}
} catch (CameraAccessException e) {
ToastUtils.showToast(appContext, "不能访问相机");
activity.finish();
} catch (NullPointerException e) {
ToastUtils.showToast(appContext, "当前设备不支持Camera2 API");
} catch (InterruptedException e) {
throw new RuntimeException("在锁住相机开启期间被打断.");
}
}
}
@Override
protected void configureTransform(Activity activity, int viewWidth, int viewHeight) {
TextureView textureView = getTextureView();
if (null == textureView || null == mPreviewSize || null == activity) {
return;
}
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.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 / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
textureView.setTransform(matrix);
}
@Override
public void notifyFocusState() {
float currentZoom = camera2Manager.getZoomProportion() * maxZoom;
updateZoomRect(currentZoom);
updatePreview();
}
private Camera2VideoRecordCallBack camera2VideoRecordCallBack;
public void setCamera2VideoRecordCallBack(Camera2VideoRecordCallBack camera2VideoRecordCallBack) {
this.camera2VideoRecordCallBack = camera2VideoRecordCallBack;
}
}
Fragment中进行调用:
public class CameraFragment extends Fragment implements CameraContract.View<CameraContract.Presenter>
, View.OnClickListener, RadioGroup.OnCheckedChangeListener
, VerticalProgressBarLayout.VerticalMoveResultListener {
public static final String TAG = CameraFragment.class.getSimpleName();
private ImageView show_result_iv;
private ImageView controller_state_iv;
private VerticalProgressBarLayout verticalProgressBarLayout;
public static CameraFragment newInstance() {
CameraFragment cameraFragment = new CameraFragment();
return cameraFragment;
}
private AutoFitTextureView textureView;
private TextView show_record_tv, record_tip_circle;
private View rootView;
private CameraContract.Presenter presenter;
private Animator flashAnimator;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
this.rootView = inflater.inflate(R.layout.fragment_camera, container, false);
initView();
return this.rootView;
}
@Override
public void showToast(String content) {
ToastUtils.showToastRunUIThread(getActivity(), content);
}
private void initView() {
this.textureView = rootView.findViewById(R.id.camera_auto_fit_texture_view);
this.show_result_iv = rootView.findViewById(R.id.camera_show);
this.show_record_tv = rootView.findViewById(R.id.camera_video_record_tip_time_tv);
this.record_tip_circle = rootView.findViewById(R.id.camera_video_record_tip_bg);
this.rootView.findViewById(R.id.camera_btn).setOnClickListener(this);
this.verticalProgressBarLayout = rootView.findViewById(R.id.camera_vertical_progress_bar);
this.controller_state_iv = this.rootView.findViewById(R.id.camera_right_top_controller);
this.controller_state_iv.setTag(CameraContract.View.MODE_RECORD_FINISH);
this.controller_state_iv.setOnClickListener(this);
this.show_result_iv.setOnClickListener(this);
((RadioGroup) this.rootView.findViewById(R.id.camera_switch_radioGroup)).setOnCheckedChangeListener(this);
((RadioGroup) this.rootView.findViewById(R.id.camera_direction_radioGroup)).setOnCheckedChangeListener(this);
this.verticalProgressBarLayout.setVerticalMoveResultListener(this);
}
@Override
public void onResume() {
super.onResume();
if (presenter != null) {
presenter.onResume();
}
}
@Override
public void onPause() {
super.onPause();
if (presenter != null) {
presenter.onPause();
}
}
@Override
public void setPresenter(CameraContract.Presenter presenter) {
this.presenter = presenter;
}
@Override
public TextureView getCameraView() {
return textureView;
}
protected String filePath;
@Override
public void loadPictureResult(String filePath) {
this.filePath = filePath;
GlideLoader.loadNetWorkResource(getActivity(), filePath, show_result_iv);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
//点击按钮,进行拍照或录像
case R.id.camera_btn:
this.presenter.takePictureOrVideo();
break;
// 控制视频录制的开关,包含暂停,恢复录制
case R.id.camera_right_top_controller:
int mode = (int) controller_state_iv.getTag();
//录制状态中,可以暂停
if (mode == CameraContract.View.MODE_RECORD_START) {
this.presenter.stopRecord();
}//暂停状态,可以继续开始录制
else if (mode == CameraContract.View.MODE_RECORD_STOP) {
this.presenter.restartRecord();
}
break;
//查看大图
case R.id.camera_show:
if (!TextUtils.isEmpty(filePath)) {
PictureActivity.openActivity(getActivity(), filePath);
}
break;
default:
break;
}
}
@Override
public void setTimingShow(String timing) {
this.show_record_tv.setText(timing);
}
@Override
public void switchRecordMode(int mode) {
switch (mode) {
//录制开始
case CameraContract.View.MODE_RECORD_START:
this.show_record_tv.setVisibility(View.VISIBLE);
this.record_tip_circle.setVisibility(View.VISIBLE);
this.controller_state_iv.setImageResource(R.drawable.camera_stop_iv);
if (flashAnimator != null && flashAnimator.isRunning()) {
flashAnimator.cancel();
}
break;
//录制暂停
case CameraContract.View.MODE_RECORD_STOP:
this.controller_state_iv.setImageResource(R.drawable.camera_start_iv);
this.show_record_tv.setVisibility(View.INVISIBLE);
flashAnimator = AnimatorBuilder.createFlashAnimator(this.record_tip_circle);
flashAnimator.start();
break;
//录制完成
case CameraContract.View.MODE_RECORD_FINISH:
this.show_record_tv.setText("");
this.show_record_tv.setVisibility(View.GONE);
this.record_tip_circle.setVisibility(View.GONE);
this.controller_state_iv.setImageResource(R.drawable.camera_init_iv);
break;
default:
break;
}
this.controller_state_iv.setTag(mode);
}
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
switch (checkedId) {
//切换成录像模式
case R.id.camera_video_record_btn:
this.presenter.switchMode(Constant.MODE_VIDEO_RECORD);
break;
//切换到拍照模式
case R.id.camera_switch_picture_btn:
this.presenter.switchMode(Constant.MODE_CAMERA);
break;
//切换到前摄像头
case R.id.camera_direction_front:
this.presenter.switchCamera(Camera2Manager.CAMERA_DIRECTION_FRONT);
break;
//切换到后摄像头
case R.id.camera_direction_back:
this.presenter.switchCamera(Camera2Manager.CAMERA_DIRECTION_BACK);
break;
default:
break;
}
}
@Override
public void moveDistance(float verticalBias) {
Log.d(TAG, "moveDistance--verticalBias: " + verticalBias);
if (presenter != null) {
presenter.setManualFocus(verticalBias);
}
}
}
另外一个项目的效果图:
源码地址: