提问:
通过 DMA-BUF 将 camera 的数据送给 libdrm 的例子
ChatGPT
ChatGPT 说:
下面是一个通过 DMA-BUF 将 camera 数据送给 libdrm 的简化示例。这个过程结合了 V4L2 来获取摄像头数据,使用 DMA-BUF 作为共享内存,最后通过 libdrm 将该数据发送到 GPU 进行显示。
示例流程:
- 使用 V4L2 捕获摄像头数据,获取一个 DMA-BUF 文件描述符。
- 使用 libdrm 创建 DRM framebuffer,将 DMA-BUF 传递给显卡。
- 将 DMA-BUF 映射到显存,并渲染到屏幕上。
示例代码:
1. 打开摄像头设备并获取 DMA-BUF 文件描述符
首先,我们使用 V4L2 打开摄像头设备,并请求 DMA-BUF 类型的缓冲区。
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
int open_camera_device(const char *device) {
int fd = open(device, O_RDWR);
if (fd == -1) {
perror("Error opening camera device");
exit(1);
}
return fd;
}
int request_dma_buf(int fd, int width, int height, int format) {
struct v4l2_format fmt;
struct v4l2_requestbuffers reqbuf;
struct v4l2_buffer buf;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = format;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
perror("VIDIOC_S_FMT");
exit(1);
}
memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.count = 1; // 请求一个缓冲区
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_DMABUF; // 使用 DMA-BUF 类型的缓冲区
if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
perror("VIDIOC_REQBUFS");
exit(1);
}
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_DMABUF;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
perror("VIDIOC_QUERYBUF");
exit(1);
}
return buf.m.fd; // 返回 DMA-BUF 文件描述符
}
在这里,我们请求一个 DMA-BUF 类型的缓冲区,并通过 VIDIOC_QUERYBUF
获取 DMA-BUF 文件描述符。
2. 使用 libdrm 创建 framebuffer 并传递 DMA-BUF
然后,使用 libdrm 将 DMA-BUF 传递给显卡,并创建一个 framebuffer。
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <drm/drm_prime.h>
#include <fcntl.h>
#include <unistd.h>
int open_drm_device() {
int fd = open("/dev/dri/card0", O_RDWR);
if (fd == -1) {
perror("Error opening DRM device");
exit(1);
}
return fd;
}
void create_framebuffer(int drm_fd, int dma_buf_fd, int width, int height) {
struct drm_prime_handle prime_handle = {};
prime_handle.fd = dma_buf_fd;
uint32_t handle;
if (ioctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime_handle) == -1) {
perror("DRM_IOCTL_PRIME_FD_TO_HANDLE");
exit(1);
}
handle = prime_handle.handle;
uint32_t fb_id;
int bpp = 32; // 每像素32位,假设我们是 RGBA 格式
int pitch = width * bpp / 8;
if (drmModeAddFB(drm_fd, width, height, 24, 32, pitch, handle, &fb_id) != 0) {
perror("drmModeAddFB");
exit(1);
}
printf("Framebuffer created with ID: %u\n", fb_id);
}
这里,我们使用 DRM_IOCTL_PRIME_FD_TO_HANDLE
将 DMA-BUF 文件描述符转化为 DRM 句柄,然后通过 drmModeAddFB
创建 framebuffer。
3. 渲染数据到屏幕
最后,将 framebuffer 显示到屏幕上,使用 DRM plane。
void display_on_screen(int drm_fd, uint32_t fb_id, uint32_t plane_id, uint32_t crtc_id, int width, int height) {
if (drmModeSetPlane(drm_fd, plane_id, crtc_id, fb_id, 0, 0, 0, width, height) != 0) {
perror("drmModeSetPlane");
exit(1);
}
printf("Displaying framebuffer on screen\n");
}
drmModeSetPlane
用来将 framebuffer 绑定到一个 plane 上,这样 GPU 可以直接将数据渲染到屏幕。
主程序
在 main()
函数中,我们依次打开摄像头设备、获取 DMA-BUF 文件描述符、创建 framebuffer,并将图像显示到屏幕。
int main() {
int width = 1920;
int height = 1080;
int format = V4L2_PIX_FMT_YUYV; // 选择一个适合的像素格式
// 打开摄像头设备并请求 DMA-BUF
int v4l2_fd = open_camera_device("/dev/video0");
int dma_buf_fd = request_dma_buf(v4l2_fd, width, height, format);
// 打开 DRM 设备
int drm_fd = open_drm_device();
// 使用 DMA-BUF 创建 framebuffer
create_framebuffer(drm_fd, dma_buf_fd, width, height);
// 假设我们已经知道 plane_id 和 crtc_id
uint32_t plane_id = 0; // 这是一个示例,应该根据实际设备设置
uint32_t crtc_id = 0; // 同上
// 将图像显示到屏幕
display_on_screen(drm_fd, fb_id, plane_id, crtc_id, width, height);
// 清理工作
close(v4l2_fd);
close(drm_fd);
return 0;
}
总结:
- V4L2 通过
V4L2_MEMORY_DMABUF
获取 DMA-BUF 文件描述符,避免了内存拷贝。 - libdrm 使用
DRM_IOCTL_PRIME_FD_TO_HANDLE
将 DMA-BUF 文件描述符转换为显卡可以理解的 DRM 句柄,并创建 framebuffer。 - 通过
drmModeSetPlane
将 framebuffer 数据渲染到屏幕上。
这种方式确保了数据传输过程中的零拷贝,直接利用 GPU 显示摄像头数据,效率更高。
DeepSeep
DeepSeek 说:
通过 DMA-BUF
将相机数据传递给 libdrm
的过程涉及多个步骤,包括从相机获取数据、创建 DMA-BUF
缓冲区、将缓冲区映射到 libdrm
并进行显示。以下是一个简单的示例框架,展示如何实现这一流程。
1. 前提条件
-
确保系统支持
DMA-BUF
和libdrm
。 -
安装必要的库和工具:
-
libdrm
(用于 DRM 显示) -
v4l2-utils
(用于相机数据捕获) -
libcamera
(可选,用于现代相机支持)
-
2. 示例代码框架
以下是一个简化的示例代码框架,展示如何通过 DMA-BUF
将相机数据传递给 libdrm
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <drm_fourcc.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <gbm.h>
#include <EGL/egl.h>
// 定义相机设备
#define CAMERA_DEVICE "/dev/video0"
#define DRM_DEVICE "/dev/dri/card0"
// 初始化相机
int init_camera(int *camera_fd) {
*camera_fd = open(CAMERA_DEVICE, O_RDWR);
if (*camera_fd < 0) {
perror("Failed to open camera");
return -1;
}
// 设置相机格式(例如:V4L2_PIX_FMT_YUYV)
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(*camera_fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("Failed to set camera format");
return -1;
}
return 0;
}
// 初始化 DRM
int init_drm(int *drm_fd) {
*drm_fd = open(DRM_DEVICE, O_RDWR);
if (*drm_fd < 0) {
perror("Failed to open DRM device");
return -1;
}
// 获取 DRM 资源
drmModeRes *resources = drmModeGetResources(*drm_fd);
if (!resources) {
perror("Failed to get DRM resources");
return -1;
}
// 查找并设置 CRTC 和 Connector
// 这里省略具体实现,需要根据硬件配置选择合适的 CRTC 和 Connector
drmModeFreeResources(resources);
return 0;
}
// 创建 DMA-BUF 缓冲区
int create_dma_buf(int drm_fd, int width, int height, int *dma_buf_fd) {
// 使用 DRM 创建 GBM 缓冲区
struct gbm_device *gbm_dev = gbm_create_device(drm_fd);
if (!gbm_dev) {
perror("Failed to create GBM device");
return -1;
}
// 创建 GBM 缓冲区对象
struct gbm_bo *bo = gbm_bo_create(gbm_dev, width, height, GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!bo) {
perror("Failed to create GBM buffer object");
return -1;
}
// 获取 DMA-BUF 文件描述符
*dma_buf_fd = gbm_bo_get_fd(bo);
if (*dma_buf_fd < 0) {
perror("Failed to get DMA-BUF fd");
return -1;
}
return 0;
}
// 主函数
int main() {
int camera_fd, drm_fd, dma_buf_fd;
// 初始化相机
if (init_camera(&camera_fd) < 0) {
return -1;
}
// 初始化 DRM
if (init_drm(&drm_fd) < 0) {
return -1;
}
// 创建 DMA-BUF 缓冲区
if (create_dma_buf(drm_fd, 640, 480, &dma_buf_fd) < 0) {
return -1;
}
// 将相机数据映射到 DMA-BUF 缓冲区
// 这里省略具体实现,需要使用 V4L2 的 VIDIOC_QBUF 和 VIDIOC_DQBUF 操作
// 将 DMA-BUF 缓冲区传递给 DRM 进行显示
// 这里省略具体实现,需要使用 DRM 的 atomic 或 legacy API
// 清理资源
close(camera_fd);
close(drm_fd);
close(dma_buf_fd);
return 0;
}
3. 关键步骤说明
-
初始化相机:
-
使用 V4L2 API 打开相机设备并设置格式。
-
通过
VIDIOC_S_FMT
设置分辨率、像素格式等。
-
-
初始化 DRM:
-
打开 DRM 设备并获取资源(CRTC、Connector 等)。
-
配置显示模式。
-
-
创建 DMA-BUF 缓冲区:
-
使用 GBM(Generic Buffer Management)创建缓冲区。
-
获取 DMA-BUF 文件描述符。
-
-
映射相机数据到 DMA-BUF:
-
使用 V4L2 的
VIDIOC_QBUF
和VIDIOC_DQBUF
操作将相机数据映射到 DMA-BUF。
-
-
显示 DMA-BUF 数据:
-
使用 DRM 的 atomic 或 legacy API 将 DMA-BUF 缓冲区显示到屏幕上。
-
4. 注意事项
-
需要根据具体硬件和驱动支持调整代码。
-
如果使用现代相机(如 Raspberry Pi 的 libcamera),可能需要使用
libcamera
替代 V4L2。 -
确保系统内核支持
DMA-BUF
和DRM
。
5. 参考文档
如果有进一步的问题或需要更详细的实现,请随时提问!