两岸猿声啼不住,轻舟已过万重山—李白
-
Android Camera系列(一):SurfaceView+Camera
-
Android Camera系列(二):TextureView+Camera
-
Android Camera系列(三):GLSurfaceView+Camera
本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等,项目结构代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会
本章我们来讲解TextureView进行Camera预览,基于上一篇Android Camera系列(一):SurfaceView+Camera的成果,我们已经对Camera进行了封装,CameraManager拿来直接使用就好
一.TextureView使用
优点:
支持复杂的视图变换:与SurfaceView不同,TextureView支持包括缩放、旋转在内的各种变换操作,这些操作在视图层次中进行,使得TextureView更加灵活和适应复杂的用户界面需求。
缺点:
性能不如SurfaceView:在低端设备或高GPU负荷情况下,可能会出现掉帧或卡顿现象,低性能可以给一个参考指标大概就是10年前的设备,或者是给欠发达国家提供的低性能的海外设备
TextureView作为Camera的预览视图与SurfaceView不同,TextureView要获取SurfaceTexture,并传递给Camera作为预览容器
CameraManager针对SurfaceTexture的预览接口
/**
* 使用TextureView预览Camera
*
* @param surface
*/
@Override
public synchronized void startPreview(SurfaceTexture surface) {
Logs.i(TAG, "startPreview...");
if (isPreviewing) {
return;
}
if (mCamera != null) {
try {
mCamera.setPreviewTexture(surface);
if (!mPreviewBufferCallbacks.isEmpty()) {
mCamera.addCallbackBuffer(new byte[mPreviewWidth * mPreviewHeight * 3 / 2]);
mCamera.setPreviewCallbackWithBuffer(mPreviewCallback);
}
mCamera.startPreview();
onPreview(mPreviewWidth, mPreviewHeight);
} catch (Exception e) {
onPreviewError(CAMERA_ERROR_PREVIEW, e.getMessage());
}
}
}
- 自定义CameraTextureView继承TextureView
- 实现
TextureView.SurfaceTextureListener
接口,并在CameraTextureView初始化时设置回调 - 实现自定义CameraCallback接口,监听Camera状态
- 一定要实现
onResume
和onPause
接口,并在对应的Activity生命周期中调用。这是所有使用Camera的bug的源头
/**
* 摄像头预览TextureView
*
* @author xiaozhi
* @since 2024/8/22
*/
public abstract class BaseTextureView extends TextureView implements TextureView.SurfaceTextureListener, CameraCallback, BaseCameraView {
private static final String TAG = BaseTextureView.class.getSimpleName();
private Context mContext;
private SurfaceTexture mSurfaceTexture;
private boolean isMirror;
private boolean hasSurface; // 是否存在摄像头显示层
private ICameraManager mCameraManager;
private int mRatioWidth = 0;
private int mRatioHeight = 0;
private int mTextureWidth;
private int mTextureHeight;
public BaseTextureView(Context context) {
super(context);
init(context);
}
public BaseTextureView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BaseTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mContext = context;
mCameraManager = createCameraManager(context);
mCameraManager.setCameraCallback(this);
setSurfaceTextureListener(this);
}
public abstract ICameraManager createCameraManager(Context context);
/**
* 获取摄像头工具类
*
* @return
*/
public ICameraManager getCameraManager() {
return mCameraManager;
}
/**
* 是否镜像
*
* @return
*/
public boolean isMirror() {
return isMirror;
}
/**
* 设置是否镜像
*
* @param mirror
*/
public void setMirror(boolean mirror) {
isMirror = mirror;
requestLayout();
}
private void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, width * 4 / 3);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
if (isMirror) {
Matrix transform = new Matrix();
transform.setScale(-1, 1, getMeasuredWidth() / 2, 0);
setTransform(transform);
} else {
setTransform(null);
}
}
/**
* 获取SurfaceTexture
*
* @return
*/
@Override
public SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Logs.i(TAG, "onSurfaceTextureAvailable.");
mTextureWidth = width;
mTextureHeight = height;
mSurfaceTexture = surfaceTexture;
hasSurface = true;
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
Logs.i(TAG, "onSurfaceTextureSizeChanged.");
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
Logs.v(TAG, "onSurfaceTextureDestroyed.");
closeCamera();
hasSurface = false;
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
/**
* 打开摄像头并预览
*/
@Override
public void onResume() {
if (hasSurface) {
// 当activity暂停,但是并未停止的时候,surface仍然存在,所以 surfaceCreated()
// 并不会调用,需要在此处初始化摄像头
openCamera();
}
}
/**
* 停止预览并关闭摄像头
*/
@Override
public void onPause() {
closeCamera();
}
@Override
public void onDestroy() {
}
/**
* 初始化摄像头,较为关键的内容
*/
private void openCamera() {
if (mSurfaceTexture == null) {
Logs.e(TAG, "mSurfaceTexture is null.");
return;
}
if (mCameraManager.isOpen()) {
Logs.w(TAG, "Camera is opened!");
return;
}
mCameraManager.openCamera();
}
private void closeCamera() {
mCameraManager.releaseCamera();
}
@Override
public void onOpen() {
mCameraManager.startPreview(mSurfaceTexture);
}
@Override
public void onOpenError(int error, String msg) {
}
@Override
public void onPreview(int previewWidth, int previewHeight) {
if (mTextureWidth > mTextureHeight) {
setAspectRatio(previewWidth, previewHeight);
} else {
setAspectRatio(previewHeight, previewWidth);
}
}
@Override
public void onPreviewError(int error, String msg) {
}
@Override
public void onClose() {
}
}
1.Camera操作时机
- 在
onSurfaceTextureAvailable
回调中打开Camera,在onSurfaceTextureDestroyed
中关闭摄像头 - 一定要记得在
onResume
中也打开摄像头,onPause
中关闭摄像头。
再次强调
onResume
和onPuase
中一定也要对Camera进行打开关闭操作。SurfaceTexture的回调和SurfaceHolder不同,页面不显示时SurfaceHolder会destroy,而SurfaceTexture并不会回调onSurfaceTextureDestroyed
。如果我们按Home键回到桌面打开系统相机,然后再次进入我们的应用你会发现预览黑屏,这就是没有正确在生命周期中关闭Camera导致。这也是很多别的开源项目正常用没问题,随便退出再进来总有Bug的源头。
2.TextureView计算大小
基本上和CameraSurfaceView一样,我们在onPreview
回调中设置TextureView的大小和比例即可
二.最后
本文介绍了Camera+TextureView
的基本操作及关键代码。本章内容不是很多,这得益于我们上一章定义好了CameraManager
的功劳。下一章介绍GLSurfaceView同样也是用第一章的CameraManager,嘻嘻嘻。
lib-camera库包结构如下:
包 | 说明 |
---|---|
camera | camera相关操作功能包,包括Camera和Camera2。以及各种预览视图 |
encoder | MediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制 |
gles | opengles操作相关 |
permission | 权限相关 |
util | 工具类 |
每个包都可独立使用做到最低的耦合,方便白嫖
github地址:https://github.com/xiaozhi003/AndroidCamera,https://gitee.com/xiaozhi003/android-camera
参考:
- https://github.com/afei-cn/CameraDemo
- https://github.com/saki4510t/UVCCamera
- https://github.com/google/grafika