使用Camera2开发相机,拍照录像,3A设置、输出yuv、dng图像。
主活动布局
主界面注册一个ViewPager,通过滑动转换拍照和录像界面.
拍照界面布局
1.整体的预览控件TextureView
2.左下角的缩略图ImageView
3.中间的拍照按钮ImageButton
4.切换摄像头的ImageButton
5.通过ViewPage 左右滑动切换拍照模式跟录像模式。
录像界面布局
1.整体的预览控件TextureView
2.左下角的缩略图ImageView
3.中间的拍照按钮ImageButton
4.切换摄像头的ImageButton
5.顶部的计时器Chronometer
相册界面布局
- 一个整体控件ImageView
主活动
做了初始化视图界面添加两个碎片TakePictureFragment和RecorderVideoFragment
因为ViewPage要用到PagerAdapter 将默认的界面设置成了拍照预览界面
onCreate中做了个沉浸式通知栏(隐藏状态栏和通知栏, 可用可不用)
onDestroy中调用了TakePictureFrament中的closeCamera完成释放资源.
拍照
获取权限:
1.版本判断,当手机系统大于23时,才去判断是否获取.
2.判断权限是否授予,全部授予打开相机.没有就添加到权限集合.继续申请权限.没有权限回调第一次安装程序申请权限之后还是不会进入预览界面,重启程序后才能正常预览拍照
3.setUserVisibleHint函数保证Frahment在可见的时候对其初始化. 不然两个Frgment同事执行初始化相机会出现报错.
4.initView(),初始化控件
5.onclick()监听控件
回调函数:
1.初始化Surface并在其回调函数中执行配置相机打开相机等操作
2.摄像头状态回调, 使用的是默认的TextureView所以直接写回调函数TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener()配置打开成功获取到camera设备、打开失败、打开错误操作
3.通过CameraDevice.StateCallback监听摄像头的状态(主要包括onOpened、onClosed、onDisconnected、onErro四种状态).
4.使用CameraCaptureSession.CapyureCallback捕获回调,用于接收捕获请求状态的回调.当请求触发捕获,捕获完成或是捕获图像发生错误,都会触发该回调函数对应的方法.
配置相机:
1.setupCamera()设置摄像头参数选择相机ID, 获取CameraManager类, 遍历有摄像头.
2.getOptimalSize()选择sizeMap中大于并且接近width和height的size.或是自己设定一个指定的值.
打开相机和关闭相机:
1.openCamera(),通过CameraManager类中getSystemService-
(CAMERA_SERVICE)打开相机.检查权限.
2.closeCamera().
开启相机预览:
1.预览需要设置setupImageReader(),图片阅读器.
2.创建预览通过cameraDevice创建createCaptureSession创建预览申请中有用到CameraCaptureSession.StateCallback回调, 在这个回调函数中的onConfigured获取到mCaptureSession(捕捉会话)并在其中调用重复预览.
3.重复预览请求通过captureRequest.Builder获取到预览请求的build再通过mPreviewRequestBuilder.build();获取到预览请求mPreviewRequest再通过mCaptureSession.setRepeatingRequest-
(mPreviewRequest, mPreviewCaptureCallback, null);实现反复进行预览请求以及反复捕获请求会话 在demo中repeatPreview()中实现
拍照:
- takePhoto(),首先请求CaptureRequest,
接下来又会调用CameraCaputureSession.CaptureCallback定义的
mCaptureSession进行拍照处理.
创建子线程保存图片
ImageSaver类开启子线程,目的是为了拍完照片就保存图片.然后广播通知相册刷新.
Broadcast()函数,广播通知相册更新 , 通过intent加url实现
切换前后摄像头
changeCamera()
设置缩略图
1.缩略图显示最新图片,也就是相册最后一张图片, 通过setLastImagePath()和setImageBitmap()实现.
2.GetImageFilePath获取相册camera图片路径.遍历系统相册下的所有文件路径
3.OpenAlbum打开相册,同过intent启动ImageshowActivity.class.
调用相册
创建图片集合
转到图库:
GotoFallert(),通过
intent(“com.android.camera.action.REVIEW”,url)可以实现调用相册功能.
获取uri
getMdeiaUriFromPath(),判断路径含有jpg还是MP4.最后返回uri.
录像
1.使用setUserVisibleHint对Fragment可见的时候对其进行初始化,不然两个Fragment同时执行初始化相机打开相机等会报错
2.initView初始化控件
3.onClick监听控件
4.回调摄像头和录像时会话状态
5.InitTextureView(),初始化TextureView,让TextureView接收摄像头预览画面
6.计算摄像头分辨率
7.InitCameraManager()初始化相机管理CameraManager,获取摄像头分辨率/方向/ID与打开摄像头的工作.
8.SelectCamera(),选择摄像头,使用前置还是后置.
9.openCamera() 还是一样openCamera 的时候需要用到
CameraDevice.StateCallback 在里面 开启预览
10.开启预览 这里是把创建预览请求mPreviewCaptureRequest =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewCaptureRequest.addTarget(previewSurface);
写到了startPreview()中捕获请求的回调
CameraCaptureSession.StateCallback() 也写到这个方法里在回调中调用重复预览 repeatPreview()
11.重复预览 repeatPreview()
mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(), null, mChildHandler);mChildHandler这里是创建了一个子线程处理
12.因为录制视频需要停止预览stopPreview()
13.初始化子线程 initChildHandler()
以及关闭线程stopBackgroundThread()
14.配置录制视频的相关数据configMediaRecorder()
mMediaRecorder.setOrientationHint(270); 通过这个来处理保存的视频方向前置需要设为270 后置需要设置90
15.配置录制视频的CameraCaptureSession 这里跟拍照不一样configSession()
16.开始录制startRecorder()
停止录制stopRecorder()
17.广播刷新相册broadcast()
18. 改变前后摄像头changeCamera()
19.设置显示最后一样图片设置最后一张路径setLastImagePath() 把路径转图片显示setImageBitmap
20.打开相册 openAlbum()
21.录制视频需要用的计时器控件startTime() endTime()
CAMERA2主要类说明
CameraManager
通过Context.getSystemService(CameraManager.calss)或者
Context.getSystemService(Context.CAMERA_SERVICE)来完成初始化 ,用于管理系统摄像头:
l 通过getCameraIdList()方法获取Android设备的摄像头列表
l getCameraCharacteristics(String cameraId)获取摄像头的详细参数和支持的功能
l openCamera(String cameraId CameraDevice.StateCallback callback, Handler handler)打开指定Id的摄像头
CameraDevice
CameraDevice是Camera2中抽象出来的一个对象,直接与系统硬件摄像头相联系。因为不可能所有的摄像头都会支持高级功能(即摄像头功能可被分为limit 和full 两个级别),当摄像头处于limited 级别时候,此时Camera2和早期的Camera功能差不多,除此之外在Camera2架构中,CameraDevice还承担其他两项重要任务:
l 通过CameraDevice.StateCallback监听摄像头的状态(主要包括onOpened、onClosed、onDisconnected、onErro四种状态)
l 管理CameraCaptureSession,
通过方法createCaptureSession(List<Surface> outputs,
CameraCaptureSession.StateCallback callback, Handler handler)
方法和createReprocessableCaptureSession
(InputConfiguration inputConfig, List<Surface> outputs,
CameraCaptureSession.StateCallback callback, Handler handler)方法创建会话 (其中第三个参数: The handler on which the callback should be invoked, or null to use the current thread's looper.),通常会在CameraDevice.StateCallback中调用对应方法创建预览会话。
l 管理CaptureRequest,主要包括通过createCaptureRequest(int templateType)创建捕获请求,在需要预览、拍照、再次预览的时候都需要通过创建请求来完成。
CameraCaputureSession
系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata,这一切都是在由对应的CameraDevice创建的CameraCaptureSession 会话完成,当程序需要预览、拍照、再次预览时,都需要先通过会话。
l 管理CameraCaptureSession.StateCallback状态回调
l 管理CameraCaptureSession.CaptureCallback捕获回调
l 调用方法capture(CaptureRequest request,
CameraCaptureSession.CaptureCallback listener, Handler handler)提交捕获图像请求
l 调用方法setRepeatingRequest(CaptureRequest request,
CameraCaptureSession.CaptureCallback listener, Handler handler)请求不断重复捕获图像,即实现预览
l 通过方法调用stopRepeating()实现停止捕获图像,即停止预览。
CameraRequest与CameraRequest.Builder
CameraRequest代表了一次捕获请求,而CameraRequest.Builder用于描述捕获图片的各种参数设置,包含捕获硬件(传感器,镜头,闪存),对焦模式、曝光模式,处理流水线,控制算法和输出缓冲区的配置。,然后传递到对应的会话中进行设置,CameraRequest.Builder则负责生成CameraRequest对象。当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest可以通过CameraRequest.Builder来进行初始化,通过调用createCaptureRequest来获得。
CameraCharacteristics
描述Cameradevice属性的对象,可以使用CameraManager通过getCameraCharacteristics(String cameraId)进行查询。
CaputureResult
CaptureRequest描述是从图像传感器捕获单个图像的结果的子集的对象。当CaptureRequest被处理之后由CameraDevice生成。
Camera2中3A等设置
Camera2中CaptureRequest的3A设置
自动曝光、聚焦、白平衡
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO);
switch监听器控制开启关闭聚焦、曝光。
Seekbar监听器拖动控制聚焦、曝光、放大缩小
闪光灯开启关闭
mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
camera2回调imagereader
从image拿到YUV数据再转化成RGB保存
设置返回数据格式为YUV_420_888, ImageReader会得到三个Plane,分别对应y,u,v,每个Plane都有自己的规格。
ImageUtil.java
import android.graphics.ImageFormat;
import android.media.Image;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import java.nio.ByteBuffer;
public class ImageUtil {
public static final int YUV420P = 0;
public static final int YUV420SP = 1;
public static final int NV21 = 2;
private static final String TAG = "ImageUtil";
/***
* 此方法内注释以640*480为例
* 未考虑CropRect的
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static byte[] getBytesFromImageAsType(Image image, int type) {
try {
//获取源数据,如果是YUV格式的数据planes.length = 3
//plane[i]里面的实际数据可能存在byte[].length <= capacity (缓冲区总大小)
final Image.Plane[] planes = image.getPlanes();
//数据有效宽度,一般的,图片width <= rowStride,这也是导致byte[].length <= capacity的原因
// 所以我们只取width部分
int width = image.getWidth();
int height = image.getHeight();
//此处用来装填最终的YUV数据,需要1.5倍的图片大小,因为Y U V 比例为 4:1:1
byte[] yuvBytes = new byte[width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
//目标数组的装填到的位置
int dstIndex = 0;
//临时存储uv数据的
byte uBytes[] = new byte[width * height / 4];
byte vBytes[] = new byte[width * height / 4];
int uIndex = 0;
int vIndex = 0;
int pixelsStride, rowStride;
for (int i = 0; i < planes.length; i++) {
pixelsStride = planes[i].getPixelStride();
rowStride = planes[i].getRowStride();
ByteBuffer buffer = planes[i].getBuffer();
//如果pixelsStride==2,一般的Y的buffer长度=640*480,UV的长度=640*480/2-1
//源数据的索引,y的数据是byte中连续的,u的数据是v向左移以为生成的,两者都是偶数位为有效数据
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
int srcIndex = 0;
if (i == 0) {
//直接取出来所有Y的有效区域,也可以存储成一个临时的bytes,到下一步再copy
for (int j = 0; j < height; j++) {
System.arraycopy(bytes, srcIndex, yuvBytes, dstIndex, width);
srcIndex += rowStride;
dstIndex += width;
}
} else if (i == 1) {
//根据pixelsStride取相应的数据
for (int j = 0; j < height / 2; j++) {
for (int k = 0; k < width / 2; k++) {
uBytes[uIndex++] = bytes[srcIndex];
srcIndex += pixelsStride;
}
if (pixelsStride == 2) {
srcIndex += rowStride - width;
} else if (pixelsStride == 1) {
srcIndex += rowStride - width / 2;
}
}
} else if (i == 2) {
//根据pixelsStride取相应的数据
for (int j = 0; j < height / 2; j++) {
for (int k = 0; k < width / 2; k++) {
vBytes[vIndex++] = bytes[srcIndex];
srcIndex += pixelsStride;
}
if (pixelsStride == 2) {
srcIndex += rowStride - width;
} else if (pixelsStride == 1) {
srcIndex += rowStride - width / 2;
}
}
}
}
// image.close();
//根据要求的结果类型进行填充
switch (type) {
case YUV420P:
System.arraycopy(uBytes, 0, yuvBytes, dstIndex, uBytes.length);
System.arraycopy(vBytes, 0, yuvBytes, dstIndex + uBytes.length, vBytes.length);
break;
case YUV420SP:
for (int i = 0; i < vBytes.length; i++) {
yuvBytes[dstIndex++] = uBytes[i];
yuvBytes[dstIndex++] = vBytes[i];
}
break;
case NV21:
for (int i = 0; i < vBytes.length; i++) {
yuvBytes[dstIndex++] = vBytes[i];
yuvBytes[dstIndex++] = uBytes[i];
}
break;
}
return yuvBytes;
} catch (final Exception e) {
if (image != null) {
image.close();
}
Log.i(TAG, e.toString());
}
return null;
}
/***
* YUV420 转化成 RGB
*/
public static int[] decodeYUV420SP(byte[] yuv420sp, int width, int height)
{
final int frameSize = width * height;
int rgb[] = new int[frameSize];
for (int j = 0, yp = 0; j < height; j++) {
int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
for (int i = 0; i < width; i++, yp++) {
int y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0)
y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp++]) - 128;
}
int y1192 = 1192 * y;
int r = (y1192 + 1634 * v);
int g = (y1192 - 833 * v - 400 * u);
int b = (y1192 + 2066 * u);
if (r < 0)
r = 0;
else if (r > 262143)
r = 262143;
if (g < 0)
g = 0;
else if (g > 262143)
g = 262143;
if (b < 0)
b = 0;
else if (b > 262143)
b = 262143;
rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000)
| ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
}
}
return rgb;
}
}
拍摄DNG图片
获取Raw格式数据ImageFormat.RAW_SENSOR,
使用dngCreator.writeImage(rawFileOutputStream, mImage);
文件扩展名修改为.dng
int format = mImage.getFormat();
switch (format) {
case ImageFormat.JPEG: {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
FileOutputStream output = null;
try {
output = new FileOutputStream(mFile);
output.write(bytes);
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
closeOutput(output);
}
break;
}
case ImageFormat.RAW_SENSOR: {
DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
FileOutputStream output = null;
try {
output = new FileOutputStream(mFile);
dngCreator.writeImage(output, mImage);
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
closeOutput(output);
}
break;
}
default: {
Log.e(TAG, "Cannot save image, unexpected image format:" + format);
break;
}
}
mReader.close();
相关资源:https://www.jianshu.com/p/d83161e77e90
https://www.cnblogs.com/Jackie-zhang/p/10084947.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~