编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.原生相机采集流程
  • 02.音频基础知识点
  • 05.MediaPlayer原理

01.原生相机采集流程

目录介绍

  • 01.基础概念的说明
    • 1.1 相机打开和采集
    • 1.2 CameraManager说明
    • 1.3 CameraDevice说明
    • 1.4 ImagerReader说明
  • 02.打开相机的步骤
    • 2.1 如何打开相机
    • 2.2 获取设备相机
    • 2.3 打开相机后采集
    • 2.4 获取采集图像
  • 03.图像采集编解码
    • 3.1 图像采集数据
    • 3.2 如何理解YUV
    • 3.3 YUV采样格式
    • 3.4 YUV图示分析
    • 3.5 MJPG格式图示
    • 3.6 图像编码转化
  • 05.广角采集的步骤

01.基础概念说明

1.1 相机打开和采集

打开相机和采集涉及的类

  • CameraManager 用于打开相机和检测相关的功能
  • CameraDevice 打开相机后,相机的实体对象,用于操作相机捕获,相机回调等操作
  • ImagerReader 专门做图像读取采集的,最终捕获图像是Image格式

相机图像渲染涉及的类

SurfaceView或TextureView。相机预览图像发送到SurfaceView或TextureView渲染出图像

1.2 CameraManager说明

CameraManager类概述:CameraManager是用于检测、表征和连接到 CameraDevices 的系统服务管理器。专门用于 检测 和 打开相机,以及 获取相机设备特性。

CameraManager 是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个 CameraManager 的关键功能:

  • 1)、将相机信息封装到 Camera Characteristics 中,并提获取 CameraCharacteristics 实例的方式。
  • 2)、根据指定的相机 ID 打开相机设备(openCamera)。
  • 3)、提供将闪光灯设置成手电筒模式的快捷方式。

AvailabilityCallback抽象类

  • 注册相机设备变为可用或不可打开的回调。
  • 当不再使用相机或连接新的可拆卸相机时,相机将变得可用。 当某些应用程序或服务开始使用相机时,或者当可移动相机断开连接时,它们将变得不可用。

TorchCallback抽象类

  • 相机闪光灯模式变得不可用、禁用或启用的回调。
  • 当手电筒模式处于禁用或启用状态时,才可通过 setTorchMode 设置。扩展此回调并将子类的实例传递给 registerTorchCallback 以收到此类状态更改的通知。

String[] getCameraIdList()

  • 获取当前连接的相机设备列表,这个 id 通常都是从 0 开始并依次递增的。对于一般的手机而言:
  • 后置摄像头一般为 “0”,常量值为 CameraCharacteristics.LENS_FACING_FRONT;
  • 前置摄像头一般为 “1”,常量值为 CameraCharacteristics.LENS_FACING_BACK。

CameraCharacteristics getCameraCharacteristics(String cameraId)

  • 根据 cameraId 获取对应相机设备的特征。返回一个 CameraCharacteristics,类比于旧 API 中的 Camera.Parameter 类,里面封装了相机设备固有的所有属性功能。

void openCamera(String cameraId, final CameraDevice.StateCallback callback, Handler handler)

  • 打开指定的相机设备,该方法使用当前进程 uid 继续调用 openCameraForUid(cameraId, callback, handler, USE_CALLING_UID)方法。
  • handler : 指定回调执行的线程。传 null 时默认使用当前线程的 Looper,我们通常创建一个后台线程来处理。

1.3 CameraDevice说明

CameraDevice类概述:CameraDevice 类表示连接到 Android 设备的单个相机,允许以高帧速率对图像捕获和后处理进行细粒度控制。

如何获取CameraDevice实例?通过 CameraManager 的 openCamera() 方法打开相机,在 CameraDevice.StateCallback 的 onOpened(CameraDevice camera)方法中可获得 CameraDevice 的实例。

内部类CameraDevice.StateCallback说明。当相机状态发生变化时,会调用该类相应的回调方法。

  • onOpened,当相机成功打开时回调该方法,接下来可以执行创建预览的操作
  • onDisconnected,当相机断开连接时回调该方法,应该在此执行释放相机的操作
  • onError,当相机打开失败时,应该在此执行释放相机的操作
  • onClosed,当相机关闭时回调该方法,这个方法可以不用实现

CaptureRequest.Builder createCaptureRequest(int templateType)

  • 使用指定模板创建一个 CaptureRequest.Builder 用于新的捕获请求构建。

void createCaptureSession(List outputs, CameraCaptureSession.StateCallback callback, Handler handler)

  • 使用一个指定的 Surface 输出列表创建一个相机捕捉会话

1.4 ImagerReader说明

ImagerReader类介绍

  • 简单来说就是图像读取器。ImageReader可以直接获取屏幕渲染数据,得到屏幕数据是image格式。
  • 图像数据封装在Image对象中,可以同时访问多个此类对象,最多可达 maxImages构造函数参数指定的数量。
  • 通过 ImageReader 发送到 ImageReader 的新图像Surface将排队,直到通过acquireLatestImage() 或acquireNextImage()调用访问。
  • 由于内存限制,如果 ImageReader 没有以等于生产速率的速率获取和释放图像,则图像源最终将在尝试渲染到 Surface 时停止或丢弃图像。

一些核心的API说明

  • setOnImageAvailableListener,注册一个侦听器,以便在 ImageReader 中提供新图像时调用该侦听器。
  • newInstance,通过工厂方法 newInstance函数或构建器模式ImageReader.Builder并使用。
  • getSurface,获取一个Surface可用于Images为此 生产的ImageReader。Surface用于各种 API 的绘图目标。

02.打开相机的步骤

2.1 如何打开相机

  • 如何打开相机操作
    • 第一种方式:使用最原始方式,通过intent打开原生相机。
    • 第二种方式:通过CameraManager打开(openCamera)指定的相机设备。
  • 打开相机后如何如何判断相机可用与不可用
    • 通过CameraManager注册相机设备监听,具体的api是:registerAvailabilityCallback
    • onCameraAvailable。相机已经可以使用,并且回调可以使用相机的ID
    • onCameraUnavailable。之前可用的相机已无法使用,如果应用程序具有当前已断开连接的相机的活动 CameraDevice 实例,则该应用程序将收到断开连接错误。
  • 如何判断打开相机成功或异常
    • 具体可以看:CameraDevice.StateCallback 执行的回调。打开相机成功会回调到onOpened,打开相机异常会回调onError。

2.2 获取设备相机

  • 使用CameraManager对象调用getCameraIdList()
    • 获取当前连接的相机设备列表,这个 id 通常都是从 0 开始并依次递增的。对于一般的手机而言:
    • 后置摄像头一般为 “0”,常量值为 CameraCharacteristics.LENS_FACING_FRONT;
    • 前置摄像头一般为 “1”,常量值为 CameraCharacteristics.LENS_FACING_BACK。
  • 有些相机有多个,比如说四五个摄像头
    • 若要打开指定的摄像头,则必须先知道该摄像头的id才可以。

2.3 打开相机后采集

  • 在打开相机成功之后操作
    • 在openCamera打开相机后,会执行onOpened回调,这表明打开相机成功了。然后拿到当前相机对象:CameraDevice
  • 理解采集的内容是什么
    • 相机打开后,捕获的是一个个画面。那么这个时候,采集数据,是针对这种一个个画面,进行相机捕捉会话!
    • 具体则是使用:cameraDevice.createCaptureSession(surfaces, stateCallback, null);

2.4 获取采集图像

  1. 第一步:创建图像读取器。通过ImageReader.newInstance创建图像采集器,可以指定宽高和格式。
  2. 第二步:添加图像读取器监听。调用setOnImageAvailableListener,注册一个侦听器,以便在 ImageReader 中提供新图像时调用该侦听器。如果有图像,则获取图像image对象。
  3. 第三步:绑定图像读取器surface到相机上。获取ImageReader的Surface,然后添加到CameraDevice配置的Surface输出列表,最后使用相机创建一个捕捉会话。
  4. 第四步:异常情况处理。如果打开相机异常,或者相机断开连接,则关闭图像读取器。

03.图像采集编解码

3.1 图像采集数据

相机采集的数据一般是什么格式的?

  1. 在绝大多数摄像头所采用的是免驱摄像头,一般有这几种传输格式,YUV和MJPG和RGB。
  2. 一般视频采集芯片采集到数据都是按照YUV格式输出,包括很多现在很多的相机。

YUV,无压缩图像格式的视频,优点:系统资源占用少(因为不用解码),不需要解码器。缺点是帧率稍慢(受限于USB分配的带宽)

MJPG,相当于JPEG图像压缩格式,优点:帧率高(视频开启快,曝光快)。缺点是影像有马赛克,并且需要解码器,会占用系统资源

3.2 如何理解YUV

YUV也是一种颜色空间,一种编码格式,当初是为了兼容黑白电视和彩色电视机设计的。Y、U、V分别占用8位

Y 表示明亮度(Luminance、Luma),也就是灰阶值。

U、V 表示色度(Chrominance 或 Chroma),描述的是色调和饱和度

YUV图片查看器:http://datahammer.de/

3.3 YUV采样格式

YUV444 采样: 每1个Y分量对应一组UV分量,一个像素共占用(8+8+8)bit = 3byte

YUV422 采样: 每2个Y分量对应一组UV分量,一个像素共占用(8+81/2 + 81/2)bit = 2byte

YUV420 采样: 每4个Y分量对应一组UV分量,一个像素共占用(8+81/4+81/4) = 1.5byte

3.4 YUV图示分析

YUV444,每个Y,都有一组U、V对应。

  • 四个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
  • 采样的码流为: Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3
  • 映射出的像素点为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
  • 一张 1920 * 1280 大小的图片,如果按照YUV444采样,其存储占用共(192012808 + 192012808 + 192012808)/8/1024/1024 = 7.03M

YUV422,每两个Y,共用一组U、V

  • 四个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
  • 采样的码流为: Y0 U0 Y1 V1 Y2 U2 Y3 U3
  • 映射出的像素点为:[Y0 U0 V1]、[Y1 U0 V1]、[Y2 U2 V3]、[Y3 U2 V3]
  • 一张 1920 * 1280 大小的图片,如果按照YUV444采样,其存储占用共(192012808 + 1920128081/2 + 1920128081/2)/8/1024/1024 = 4.68M

YUV420,每四个Y,共用一组U、V。

  1. 对于YUV420p,在开发中,经常会遇到该格式,常见的有YV12、YV21。YV21:Y先存储,接着U在前,V在后分别存储。YV12:Y先存储,接着V在前,U在后分别存储
  2. 对于YUV420sp,在开发中也是比较常见的,比如NV21、NV12。NV21 先存储Y,然后V在前U在后交错存储【Android】。NV12 先存储Y,然后U在前V在后交错存储【iOS】
  • 图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、 [Y2 U2 V2]、 [Y3 U3 V3]、[Y5 U5 V5]、[Y6 U6 V6]、 [Y7 U7 V7] 、[Y8 U8 V8]
  • 采样的码流为: Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8
  • 映射出的像素点为:[Y0 U0 V5]、[Y1 U0 V5]、[Y2 U2 V7]、[Y3 U2 V7]、[Y5 U0 V5]、[Y6 U0 V5]、[Y7 U2 V7]、[Y8 U2 V7]
  • 一张 1920 * 1280 大小的图片,如果按照YUV444采样,其存储占用共(192012808 + 1920128081/4 + 1920128081/4)/8/1024/1024 = 3.51M

那么在实际开发中究竟选择哪一种采样格式的YUV图像数据呢?

可以从质量效果,以及同样像素图片空间占有量,这两个维度来衡量选择合适的格式图片。因此最终选择使用:NV21

3.5 MJPG格式图示

在Android中,相机采集的数据通常是以YUV格式(如NV21或YUV420)进行传输和处理。如果需要将相机采集的数据转换为MJPG格式,可以使用以下步骤:

  1. 转换为MJPG格式:在相机回调中,获取到的每一帧数据通常是YUV格式的字节数组。可以使用第三方库(如libjpeg-turbo)将YUV数据转换为MJPG格式的字节数组。
  2. 传输和处理MJPG数据:将转换得到的MJPG格式的字节数组进行传输或处理。可以将其保存为文件、通过网络传输或进行其他操作。

在Android应用中显示和播放相机采集的数据,通常不需要将其转换为MJPG格式。可以直接使用相机预览功能或SurfaceView来显示相机采集的YUV数据。

MJPG(Motion JPEG)是一种视频编码格式,具有以下特点:

  1. 逐帧压缩:MJPG将视频流分解为一系列独立的JPEG图像帧,每一帧都是独立的压缩图像。这意味着每一帧都可以单独解码和显示,不需要依赖前后帧的信息。
  2. 无损压缩:JPEG是一种无损压缩格式,它通过去除图像中的冗余信息来减小文件大小,但不会丢失图像的质量。因此,MJPG可以提供较高的图像质量。
  3. 可随机访问:由于每一帧都是独立的JPEG图像,可以通过索引或跳帧来实现随机访问。这使得在视频中快速定位和播放特定帧变得更加容易。
  4. 相对较低的压缩比:相对于其他视频编码格式(如H.264),MJPG的压缩比较低。这意味着MJPG生成的文件大小可能会比较大,占用更多的存储空间和带宽。

由于MJPG每一帧都是独立的压缩图像,因此它在压缩效率和带宽利用率方面可能不如其他视频编码格式。

3.6 图像编码转化

要做的yuv数据处理,无非就是针对各种图像格式下yuv数据(byte[])的转换、调整。

NV21:安卓的模式。存储顺序是先存Y,再存U,再VU交替存储,格式为:YYYYVUVUVU。https://blog.csdn.net/haiping1224746757/article/details/109815884

// NV21: YYYYVUVU NV12: YYYYUVUV
public static byte[] ImageToNV21Byte(Image image) {
    Image.Plane[] planes = image.getPlanes();
    int width = image.getWidth();
    int height = image.getHeight();
    byte[] data = new byte[width * height * 3 / 2];
    //planes[0] 总是Y ,planes[1] 总是U(Cb), planes[2]总是V(Cr)。
    ByteBuffer yBuffer = planes[0].getBuffer();
    ByteBuffer vuBuffer = planes[2].getBuffer();
    int ySize = yBuffer.remaining();
    int vuSize = vuBuffer.remaining();
    yBuffer.get(data, 0, ySize);
    vuBuffer.get(data, ySize, vuSize);
    return data;
}

如何区分NV12和NV21?

  • plane[0] + plane[1] 可得NV12。
  • plane[0] + plane[2] 可得NV21

04.遇到的坑分析

4.1 辨别硬件支持等级

  • 在 Camera2 中,相机设备支持的硬件等级有
    • LEVEL_3 > FULL > LIMIT > LEGACY
    • 对于大多数的Android设备而言,都会支持到 FULL 或 LIMIT。当支持到 FULL 等级的相机设备,将拥有比旧 API 强大的新特性,如 30fps 全高清连拍,帧之间的手动设置,RAW 格式的图片拍摄,快门零延迟以及视频速拍等,否则和旧 API 功能差别不大。
  • 如何获取相机的特性
    • 通过 CameraCharacteristics 类获取相机设备的特性,包括硬件等级的支持等级。

05.广角采集的步骤

  • https://blog.csdn.net/Gaga246/article/details/128654820

5.3 参考博客连接

  • Android Camera之createCaptureSession()
    • https://blog.csdn.net/weixin_42463482/article/details/119858829
  • 相机Camera官方文档
    • https://developer.android.google.cn/reference/android/hardware/camera2/package-summary
  • Android音视频——Libyuv使用实战
    • https://zhuanlan.zhihu.com/p/666127328
    • https://blog.csdn.net/weekend_y45/article/details/125079916
贡献者: yangchong211
下一篇
02.音频基础知识点