libcamera 简介

libcamera 是一个开源的多媒体库,用于在 Linux 操作系统上支持多种摄像头硬件的访问,libcamera 是一个用户空间库,可用于开发基于摄像头的应用程序,如相机应用程序或视频通信应用程序。

基础概念

CameraManager

CameraManager 负责列举系统中的所有摄像头

  • 调用 CameraManager::cameras() 返回一组应用程序可用的摄像头
  • 调用 CameraManager::get() 通过摄像头 ID 来获取对应的摄像头
std::unique_ptr<CameraManager> cm = std::make_unique<CameraManager>();

cm->start();

for (auto const &camera : cm->cameras())
std::cout << " - " << camera.id() << std::endl;

进程空间中只能存在单一的 CameraManager

Camera

Camera 代表系统中可用的一个图像源,比如图像传感器

系统中存在多个摄像头且可以彼此独立操作的时候,libcamera 将它们暴露为多个 Camera 设备,硬件限制(比如内存带宽、CPU 占用率等)允许的情况下,支持多个摄像头的并行操作

Camera 设备在某个时刻只能被单一进程所使用

  • 应用程序使用 Camera 时要先调用 Camera::acquire() 对它上锁
  • 使用结束后要调用 Camera::release() 释放它

调用 Camera::id() 获得摄像头的 ID,它是字符串类型,保证唯一且稳定

  • 只要同一个摄像头以同样的方式(比如插到同一个 USB 接口上)连接到系统上,不管是插拔还是系统重启,ID 都是不变的
std::cout << camera->id() << std::endl;

这里我们打印摄像头的 ID,得到 /base/soc/i2c0mux/i2c@1/imx219@10

调用 Camera::properties() 获得相机的属性列表

  • properties 是描述相机功能的静态信息,在 Camera 对象的整个生命周期内保持不变
  • 比如摄像头的 Location(前置摄像头、后置摄像头或可自由移动的摄像头),Model(传感器的名字)
const ControlList &props = camera->properties();
const auto &model = props.get(properties::Model);
if (model)
name = " '" + *model + "'";

这里我们打印传感器的 Model,输出的结果是 'imx219'

Stream

libcamera 支持并发多路视频流

  • 例如,使用中等分辨率的视频流来做预览,同时使用最大分辨率的视频流来拍照
 +-------------------------------------------------------+
| Camera |
| +-----------+ |
| +--------+ | |------> [ Main output ] |
| | Image | | | |
| | |---->| ISP |------> [ Viewfinder ] |
| | Source | | | |
| +--------+ | |------> [ Still Capture ] |
| +-----------+ |
+-------------------------------------------------------+

摄像头流的数量和能力是平台相关的属性,pipeline handler 的实现有责任正确的汇报它

CameraConfiguration

Camera 会根据应用场景来创建一组默认配置模板,然后应用程序可以修改默认参数,并验证确保摄像头能支持新配的参数(某些参数组合可能是系统不支持的)

  • Camera::generateConfiguration() 根据 StreamRole 产生摄像头默认配置

    • StreamRole 就是这个流是被拿来干嘛的,包括

      • Raw

        • 从传感器获得原始帧(raw frames)
      • StillCapture
        • 高分辨率、高质量、低帧率的静态图像
        • 可以用闪光灯曝光
      • VideoRecording
        • 用来录像(recording)或在线播放(streaming)
        • 可能是高帧率,可以做视频稳定(video stabilization)
      • Viewfinder
        • 用于捕获视频,以便在本地屏幕上显示
        • 质量和系统资源使用之间的权衡是可以接受的
    • 每个 StreamRole 都对应一个默认的 StreamConfiguration
  • 应用程序可以修改配置参数
  • CameraConfiguration::validate() 验证并调整配置到最接近的合法配置
  • Camera::configure() 用来修改 Camera 配置,只接受完全有效的配置,对于非法的配置会报错
  • CameraConfiguration 是实现了迭代器,可以用它来遍历所有的 StreamConfiguration,也可以通过用下标调用 CameraConfiguration::at() 获取某个 StreamConfiguration
std::unique_ptr<CameraConfiguration> config =
camera->generateConfiguration( { StreamRole::Viewfinder } ); StreamConfiguration &viewFinderStreamConfig = config->at(0); std::cout << "Default viewfinder configuration is: "
<< viewFinderStreamConfig.toString() << std::endl;

这里我们打印 Viewfinder、StillCapture、Raw、VideoRecording 的默认配置

Default viewfinder configuration is: 800x600-NV12
Default stillcapture configuration is: 3280x2464-NV12
Default raw configuration is: 3280x2464-SBGGR10_CSI2P
Default videorecording configuration is: 1920x1080-YUV420

Buffer 分配

捕获到的图像要存储到 framebuffer 中

  • framebuffer 可以是应用程序提供给库

    • 应用程序可以把 buffer 分配到任何地方,比如可以直接 display driver 分配的内存中,就会被 display driver 用来渲染
    • 通过构建 FrameBuffer 实例
  • 也可以是 Camera 内部分配并暴露给应用程序
    • 使用 FrameBufferAllocator 实例来分配 buffer
    • 根据 Camera 配置来决定 buffer 的大小和类型
FrameBufferAllocator *allocator = new FrameBufferAllocator(camera);

for (StreamConfiguration &cfg : *config) {
int ret = allocator->allocate(cfg.stream());
if (ret < 0) {
std::cerr << "Can't allocate buffers" << std::endl;
return EXIT_FAILURE;
} auto &buffers = allocator->buffers(cfg.stream());
size_t allocated = buffers.size();
std::cout << "Allocated " << allocated << " buffers for stream" << std::endl; for (auto &buffer: buffers) {
for (auto &plane : buffer->planes()) {
std::cout << "~ " << plane.length << std::endl;
}
std::cout << std::endl;
}
}

这里我们通过 FrameBufferAllocator 分配了缓冲区,采用的像素格式是 NV12,所以缓冲区有两个 planes

Allocated 4 buffers for stream
~ 480000
~ 240000 ~ 480000
~ 240000 ~ 480000
~ 240000 ~ 480000
~ 240000

捕获帧

libcamera 的视频捕获基于 Request 的概念

  • 每一帧的请求必须在摄像头中排队等待处理
  • 请求至少要和一个 Stream 关联起来
  • Buffer 要被添加到请求中
    • 请求完成后,图像数据会被写到 Buffer 里,等待应用程序消费
    • 可以给每个流都配置一个 Buffer,这样就可以同时处理 viewfinder 和 still image,或通过获取原始图像和 ISP 处理后的图像
  • 请求会有一系列 Controls,它们是可调节的参数,可以以帧为单位做调整
  • 请求完成后,可以查看元数据以确定应用于图像的捕获参数
std::unique_ptr<Request> request = camera->createRequest();
if (!request)
{
std::cerr << "Can't create request" << std::endl;
return EXIT_FAILURE;
} int ret = request->addBuffer(stream, buffer.get());
if (ret < 0)
{
std::cerr << "Can't set buffer for request"
<< std::endl;
return EXIT_FAILURE;
} ControlList &controls = request->controls();
controls.set(controls::Brightness, 0.5);

Signal&Slots

libcamera 采用了类似于 QT 的信号槽机制

  • Signals 代表类实例产生的事件,Slots 是关联到某个 Signal 上的回调函数
  • Camera 暴露出的信号
    • requestCompleted

      • 在摄像头中排队的请求完成了
    • bufferCompleted
      • 请求中的某个 Buffer 完成了
    • disconnected
      • 设备从系统上断开了
  • 应用程序必须在摄像头开始运行前,将回调函数注册到这些信号上,才能处理对应的事件
camera->requestCompleted.connect(requestComplete);

开始捕获

启动摄像头,并让请求在其中排队,摄像头管线深度被填满后,其他请求必须排队等待,直到摄像头开始交付帧

  • 对于被交付的帧,在 requestCompleted 上绑定的 Slot 会被调用
camera->start();
camera->queueRequest(request.get());

消费帧

我们可以在 Slot 中消费 Buffer 中的数据和请求的元数据

元数据

请求完成后,元数据列表中包含已完成请求的各种属性,包括传感器捕获的时间戳、传感器增益(gain)和曝光值(exposure values),或来自 IPA 的属性,如 3A 算法的状态

const ControlList &requestMetadata = request->metadata();
for (const auto &ctrl : requestMetadata) {
const ControlId *id = controls::controls.at(ctrl.first);
const ControlValue &value = ctrl.second; std::cout << "\t" << id->name() << " = " << value.toString()
<< std::endl;
}

这里我们打印所有的元数据

Request completed: Request(35:C:0/1:0)
ExposureTime = 66653
AnalogueGain = 8.000000
ColourCorrectionMatrix = [ 1.684340, -0.523863, -0.160477, -0.489361, 1.884680, -0.395319, -0.084126, -0.957826, 2.041952 ]
FrameDuration = 66729
Lux = 4.425883
AeLocked = true
ColourGains = [ 1.286008, 1.913542 ]
DigitalGain = 1.000183
ColourTemperature = 3446
SensorBlackLevels = [ 4096, 4096, 4096, 4096 ]
ScalerCrop = (0, 2)/3280x2460
SensorTimestamp = 1463504108407000

Buffer 数据

FrameBuffer::metadata() 可以获得帧的动态元数据,比如帧的状态、序号、时间戳

const Request::BufferMap &buffers = request->buffers();
for (auto bufferPair : buffers) {
// (Unused) Stream *stream = bufferPair.first;
FrameBuffer *buffer = bufferPair.second;
const FrameMetadata &metadata = buffer->metadata(); /* Print some information about the buffer which has completed. */
std::cout << " seq: " << std::setw(6) << std::setfill('0') << metadata.sequence
<< " timestamp: " << metadata.timestamp
<< " bytesused: ";
}

FrameBuffer::Plane 是存储帧单一平面的内存区域

  • FrameBuffer 有一个或多个 Plane,取决于它的像素格式

    • planar 像素格式用多个内存区域来存储帧的不同颜色分量
    • 相对应的是 packed 像素格式,所有分量交织存储,只需要一个内存区域即可
  • 用 dmabuf 文件描述符、偏移量和长度来描述这样一块内存区域
    • 多个 plane 可以用同一个 dmabuf fd 来引用,这时候就需要通过偏移量和长度来区分它们
  • 应用程序应该调用 mmap() 来映射 plane 内存以访问它的内容
size_t buffer_size = 0;
for (unsigned i = 0; i < buffer->planes().size(); i++)
{
const FrameBuffer::Plane &plane = buffer->planes()[i];
buffer_size += plane.length;
if (i == buffer->planes().size() - 1 || plane.fd.get() != buffer->planes()[i + 1].fd.get())
{
void *memory = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, plane.fd.get(), 0);
buffer_size = 0;
}
}

复用 Request

将 Buffer 设置为可复用,然后重新入队

request->reuse(Request::ReuseBuffers);
camera->queueRequest(request);

释放资源

关掉摄像头、释放资源并停止 CameraManager

camera->stop();
allocator->free(stream);
delete allocator;
camera->release();
camera.reset();
cm->stop();

参考资料

libcamera 简介的更多相关文章

  1. ASP.NET Core 1.1 简介

    ASP.NET Core 1.1 于2016年11月16日发布.这个版本包括许多伟大的新功能以及许多错误修复和一般的增强.这个版本包含了多个新的中间件组件.针对Windows的WebListener服 ...

  2. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  3. Cassandra简介

    在前面的一篇文章<图形数据库Neo4J简介>中,我们介绍了一种非常流行的图形数据库Neo4J的使用方法.而在本文中,我们将对另外一种类型的NoSQL数据库——Cassandra进行简单地介 ...

  4. REST简介

    一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式.”但是在要求详细讲述它所提出的各个约束,以及如何开始搭建REST服务时,却很少有人能够清晰地说出它到底是什么,需要遵守什么样的准则. ...

  5. Microservice架构模式简介

    在2014年,Sam Newman,Martin Fowler在ThoughtWorks的一位同事,出版了一本新书<Building Microservices>.该书描述了如何按照Mic ...

  6. const,static,extern 简介

    const,static,extern 简介 一.const与宏的区别: const简介:之前常用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量. 执行时刻:宏是预编 ...

  7. HTTPS简介

    一.简单总结 1.HTTPS概念总结 HTTPS 就是对HTTP进行了TLS或SSL加密. 应用层的HTTP协议通过传输层的TCP协议来传输,HTTPS 在 HTTP和 TCP中间加了一层TLS/SS ...

  8. 【Machine Learning】机器学习及其基础概念简介

    机器学习及其基础概念简介 作者:白宁超 2016年12月23日21:24:51 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本系列文章是作者结 ...

  9. Cesium简介以及离线部署运行

    Cesium简介 cesium是国外一个基于JavaScript编写的使用WebGL的地图引擎,一款开源3DGIS的js库.cesium支持3D,2D,2.5D形式的地图展示,可以自行绘制图形,高亮区 ...

  10. 1.Hibernate简介

    1.框架简介: 定义:基于java语言开发的一套ORM框架: 优点:a.方便开发;           b.大大减少代码量;           c.性能稍高(不能与数据库高手相比,较一般数据库使用者 ...

随机推荐

  1. Docker | 容器互联互通

    上篇讲到创建自定义网络,我创建了 mynet 网络,并指定了网关和子网地址.在上篇结尾呢,我抛出了一个问题:其它网络下的容器可以直接访问mynet网络下的容器吗?今天就让我们一块看下怎么实现容器互联. ...

  2. Vue学习之--------计算属性(2022/7/9)

    文章目录 1.计算属性 1.1 计算属性实现 1.1.1 基础知识 1.1.2 代码实例 1.1.3 测试效果 1.2 计算属性简写 1.2.1 简写代码 1.3 使用插值语法实现 1.3.1 代码实 ...

  3. 8.gitlab服务器搭建(基于centos7)

    gitlab服务硬件要求 建议服务器最低配置:2核 2G以上内存(不包含2GB,2GB内存运行的时候内存直接爆掉) 官网给出的推荐配置:4核 4GB内存 支持500个用户,8核 8GB内存 支持100 ...

  4. SpringBoot自定义注解+异步+观察者模式实现业务日志保存

    一.前言 我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,但是考虑到程序的流畅和效率,我们可以使用异步进行保存,小编最近在spring和sprin ...

  5. 驱动开发:内核LDE64引擎计算汇编长度

    本章开始LyShark将介绍如何在内核中实现InlineHook挂钩这门技术,内核挂钩的第一步需要实现一个动态计算汇编指令长度的功能,该功能可以使用LDE64这个反汇编引擎,该引擎小巧简单可以直接在驱 ...

  6. [Pyhton] SimPy 离散事件模拟框架详解 —— 以一个简单的汽车充电排队模拟为例

    目录 一.背景知识 二.SimPy 讲解 2.1 SimPy 概述 2.2 基本概念 2.3 一个汽车开开停停的例子 2.4 在走走停停过程中增加充电过程(过程交互) 2.5 共享资源 三.后续 参考 ...

  7. 现代GPGPU 架构汇总

    本篇是GPGPU 架构汇总的总章,参考的是AMD公布OpenCL 手册,该手册总结了AMD. Nvdia 早年的GPGPU体系架构,以及Opencl 各个API 与硬件结构的映射关系.本篇除了整理这两 ...

  8. 微信支付v3接口的 官方 Java SDK

    啰嗦几句:微信支付v3版接口麻烦吗?在对接微信支付v3接口时,本来是一件很简单的事情,其实微信支付v3接口并不是很复杂,但是微信团队的管理很混乱,给我们开发者带来了巨大的麻烦. 微信支付v3版接口对接 ...

  9. Codeforces Round #811 (Div. 3)D. Color with Occurrences

    题目大意:给出一个文章t和n个字符串s1,s2...sn: 问能否用这n个字符串将整个文章覆盖: 思路:贪心(最小区间覆盖) 记录每个字符串能够覆盖的所有位置(起点,终点,编号) 排序后贪心的求出是否 ...

  10. VBA_BASIC

    字符串相关 判断单元格是否包含特定字符串,以"P"为例. if cells(1,1) Like "*P*" Then cells(1,2) = "ha ...