使用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

 

相册界面布局

  1. 一个整体控件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()中实现

 

拍照:

  1. 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

posted @   Stringf  阅读(1825)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示