嵌入式监控【v4l2采集->vpu编码->live555推流】

本文详细介绍了一种优化嵌入式视频监控系统的方法,通过使用v4l2替代OpenCV进行摄像头数据采集,以及利用VPU硬件加速进行H.264编码,显著降低了ARMa9开发板的CPU负载,提升了视频流畅度。

嵌入式监控【v4l2采集->vpu编码->live555推流】

在上一博客中介绍了视频监控的采用库和相关架构,这篇博客作为优化和补充,主要优化的地方为由v4l2代替opencv的采集,由vpu编码代替x264

介绍

应为x264在arm a9开发版中占用大量cpu资源,导致视频卡顿,所以决定采用使用v4l2来采集uvc数据,使用vpu编码。
开发版为imx6q,版载有vpu/ipu,可以代替x264的软算法,释放cpu资源。

数据流图

v4l2 -> yuyv -> yuv420 -> vpu -> h264
数据流验证过程:

  1. v4l2采集yuyv数据,写入到文件,使用pyuv播放验证
  2. 转码yuyv到yuv420后,写入到文件,使用pyuv播放验证
  3. 将yuv420文件使用vpu编码为h264后,使用live555推流,vlc远程播放验证

一、v4l2

此处为了精简采集uvc(usb video cam)的采集过程,直接使用v4l2采集,而没有使用opencv,opencv采集的数据Mat(RGB)需要在二次转化。

1.1 确定cam的输出格式

摄象头有两种,一种是模组形式,他的输出格式多为MJPG,另一种是uvc形式,他的输出格式为yuyv(422).
如何判断摄象头类型?一般使用usb接口的摄象头为uvc,而内嵌到板子上的多为MJPG,可以使用命令工具v4l2-ctl来判断.

下面是我的usb 罗技摄象头:

root@zjy-T440:~/workStation/crossGcc/vpu/mxc_vpu_test# v4l2-ctl -d /dev/video0 --all
Driver Info (not using libv4l2):
	Driver name   : uvcvideo
	Card type     : Integrated Camera: Integrated C
	Bus info      : usb-0000:00:14.0-8
	Driver version: 5.3.18
	Capabilities  : 0x84A00001
		Video Capture
		Metadata Capture
		Streaming
		Extended Pix Format
		Device Capabilities
	Device Caps   : 0x04200001
		Video Capture
		Streaming
		Extended Pix Format
Priority: 2
Video input : 0 (Camera 1: ok)
Format Video Capture:
	Width/Height      : 640/480
	Pixel Format      : 'YUYV'
	Field             : None
	Bytes per Line    : 1280
	Size Image        : 614400
	Colorspace        : sRGB
	Transfer Function : Default (maps to sRGB)
	YCbCr/HSV Encoding: Default (maps to ITU-R 601)
	Quantization      : Default (maps to Limited Range)
	Flags             : 
Crop Capability Video Capture:
	Bounds      : Left 0, Top 0, Width 640, Height 480
	Default     : Left 0, Top 0, Width 640, Height 480
	Pixel Aspect: 1/1
Selection: crop_default, Left 0, Top 0, Width 640, Height 480
Selection: crop_bounds, Left 0, Top 0, Width 640, Height 480
Streaming Parameters Video Capture:
	Capabilities     : timeperframe
	Frames per second: 30.000 (30/1)
	Read buffers     : 0
                     brightness 0x00980900 (int)    : min=0 max=255 step=1 default=128 value=128
                       contrast 0x00980901 (int)    : min=0 max=255 step=1 default=32 value=32
                     saturation 0x00980902 (int)    : min=0 max=100 step=1 default=64 value=64
                            hue 0x00980903 (int)    : min=-180 max=180 step=1 default=0 value=0
 white_balance_temperature_auto 0x0098090c (bool)   : default=1 value=1
                          gamma 0x00980910 (int)    : min=90 max=150 step=1 default=120 value=120
           power_line_frequency 0x00980918 (menu)   : min=0 max=2 default=1 value=1
      white_balance_temperature 0x0098091a (int)    : min=2800 max=6500 step=1 default=4000 value=4000 flags=inactive
                      sharpness 0x0098091b (int)    : min=0 max=7 step=1 default=2 value=2
         backlight_compensation 0x0098091c (int)    : min=0 max=2 step=1 default=1 value=1
                  exposure_auto 0x009a0901 (menu)   : min=0 max=3 default=3 value=3
              exposure_absolute 0x009a0902 (int)    : min=4 max=1250 step=1 default=166 value=166 flags=inactive
         exposure_auto_priority 0x009a0903 (bool)   : default=0 value=1

下面为开发版摄象头:

root@imx6qsabresd:/home# v4l2-ctl -d /dev/video2 --all
Driver Info (not using libv4l2):
	Driver name   : uvcvideo
	Card type     : USB Camera
	Bus info      : usb-ci_hdrc.1-1.2
	Driver version: 4.1.15
	Capabilities  : 0x84200001
		Video Capture
		Streaming
		Extended Pix Format
		Device Capabilities
	Device Caps   : 0x04200001
		Video Capture
		Streaming
		Extended Pix Format
Priority: 2
Video input : 0 (Camera 1: ok)
Format Video Capture:
	Width/Height  : 1280/720
	Pixel Format  : 'MJPG'
	Field         : None
	Bytes per Line: 0
	Size Image    : 1843200
	Colorspace    : SRGB
	Flags         : 
Crop Capability Video Capture:
	Bounds      : Left 0, Top 0, Width 1280, Height 720
	Default     : Left 0, Top 0, Width 1280, Height 720
	Pixel Aspect: 1/1
Selection: crop_default, Left 0, Top 0, Width 1280, Height 720
Selection: crop_bounds, Left 0, Top 0, Width 1280, Height 720
Streaming Parameters Video Capture:
	Capabilities     : timeperframe
	Frames per second: 30.000 (30/1)
	Read buffers     : 0
                     brightness (int)    : min=-64 max=64 step=1 default=0 value=0
                       contrast (int)    : min=0 max=100 step=1 default=50 value=50
                     saturation (int)    : min=0 max=100 step=1 default=50 value=50
                            hue (int)<
<think>我们正在分析函数`static void *vb2_dma_sg_vaddr(void *buf_priv)`,该函数是V4L2框架中用于获取DMA散列表缓冲区的内核虚拟地址的函数。同时,我们需要将其与之前讨论的RK3588平台上的`dummy_buf->vaddr`的获取进行关联分析。 ### 1. `vb2_dma_sg_vaddr`函数的功能 该函数的主要功能是返回一个指向DMA散列表(scatter-gather)缓冲区的内核虚拟地址。在V4L2视频缓冲区管理框架中,当内存类型为`V4L2_MEMORY_MMAP`时,我们通常需要将内核空间的缓冲区映射到用户空间。但是,对于使用DMA散列表分配的缓冲区(通常用于非连续内存),我们需要一个方法来获取其在内核空间的虚拟地址,以便内核可以直接访问缓冲区数据。 ### 2. 函数实现逻辑分析 我们来看一个典型的`vb2_dma_sg_vaddr`实现(以Linux内核中的实现为例): ```c static void *vb2_dma_sg_vaddr(void *buf_priv) { struct vb2_dma_sg_buf *buf = buf_priv; // 如果已经映射过,则直接返回映射的虚拟地址 if (!buf->vaddr) buf->vaddr = vm_map_ram(buf->pages, buf->num_pages, -1, PAGE_KERNEL); return buf->vaddr; } ``` - **参数**:`buf_priv`是指向缓冲区私有数据的指针,通常是一个`vb2_dma_sg_buf`结构体。 - **结构体`vb2_dma_sg_buf`**:它通常包含以下关键成员: - `struct page **pages`:指向页面指针数组的指针,每个指针指向一个物理页面。 - `int num_pages`:缓冲区的页面数量。 - `void *vaddr`:缓冲区的内核虚拟地址。初始时为`NULL`,表示尚未映射。 - **逻辑步骤**: 1. 检查`buf->vaddr`是否已经被映射(非`NULL`)。如果是,则直接返回。 2. 如果尚未映射,则调用`vm_map_ram()`函数将一组物理页面(由`buf->pages`指向)映射到内核虚拟地址空间。这个函数会创建一个临时的内核虚拟地址映射。 3. 将映射得到的虚拟地址保存在`buf->vaddr`中,并返回。 注意:`vm_map_ram()`是Linux内核中用于将一组物理页面映射到连续内核虚拟地址空间的函数。它适用于非连续的物理页面(即散列表),并返回一个连续的虚拟地址范围。映射的创建是临时的,通常不需要显式解除映射,因为当缓冲区被释放时,会调用相应的清理函数(如`vb2_dma_sg_put_userptr`)来解除映射。 ### 3. 与RK3588的`dummy_buf->vaddr`获取的关联 在RK3588的上下文中,我们之前讨论的`dummy_buf->vaddr`的获取与上述函数有相似之处,但也有不同: - **相似点**: - 两者都是获取缓冲区的内核虚拟地址。 - 都使用了延迟映射(lazy mapping)策略:只有在第一次访问时才进行映射,之后直接返回缓存的虚拟地址。 - **不同点**: - **使用场景**: - `vb2_dma_sg_vaddr`用于V4L2视频缓冲区,特别是使用DMA散列表分配的缓冲区(非连续物理内存)。 - RK3588的`dummy_buf`可能是用于图形处理(如SurfaceFlinger)或多媒体处理(如VPU)的缓冲区,其内存分配方式可能是通过DMA-BUF框架(连续或非连续)。 - **映射方式**: - `vb2_dma_sg_vaddr`使用`vm_map_ram()`来映射一组非连续的物理页面到连续的虚拟地址。 - 在RK3588的DMA-BUF场景中,获取虚拟地址通常是通过`dma_buf_vmap()`函数(如之前引用[2]所述),该函数会调用DMA-BUF导出者提供的`vmap`操作。如果导出者使用连续物理内存,则映射简单;如果使用非连续内存,则内部可能也会使用类似`vm_map_ram`的机制。 ### 4. 关联总结 尽管两者属于不同的子系统(V4L2 vs. DRM/Graphics),但它们都遵循相似的内核内存映射原则: 1. **按需映射**:在第一次访问时建立映射。 2. **缓存映射结果**:避免重复映射的开销。 3. **处理非连续内存**:通过`vm_map_ram`或类似的函数将非连续的物理页面映射到连续的虚拟地址空间。 在RK3588的`dummy_buf`场景中,如果该缓冲区是通过DMA-BUF框架导入的,那么其`vaddr`的获取可能类似于: ```c dummy_buf->vaddr = dma_buf_vmap(dma_buf); ``` 而`dma_buf_vmap`内部可能会根据缓冲区的实际内存类型(如分配方式为连续或非连续)调用不同的映射函数。如果缓冲区是由`vb2_dma_sg`分配的(即V4L2的散列表缓冲区),那么`dma_buf_vmap`最终可能会调用到`vb2_dma_sg_vaddr`。 ### 5. 关键结论 - `vb2_dma_sg_vaddr`是V4L2框架中用于获取DMA散列表缓冲区内核虚拟地址的核心函数,通过`vm_map_ram`实现非连续物理内存到连续虚拟地址的映射。 - 在RK3588的图形/多媒体处理中,`dummy_buf->vaddr`的获取可能通过DMA-BUF的`dma_buf_vmap`实现,其底层机制与`vb2_dma_sg_vaddr`类似,但属于不同的框架层次。 §§相关问题§§ 1. 在V4L2框架中,`vb2_dma_sg`缓冲区如何管理物理内存的分配与释放? 2. `vm_map_ram`函数的工作原理是什么?它和`vmap`函数有何区别? 3. 在RK3588平台上,DMA-BUF的`vmap`操作如何适配不同类型的物理内存(连续DMA、非连续sg表)? 4. 为什么在内核中需要将非连续物理页面映射到连续虚拟地址?有哪些应用场景? </think> ### `vb2_dma_sg_vaddr`函数分析与RK3588关联 #### 一、函数功能解析 `static void *vb2_dma_sg_vaddr(void *buf_priv)`是Linux V4L2框架中用于**获取DMA散列表缓冲区的内核虚拟地址**的核心函数。其主要功能程如下: ```c static void *vb2_dma_sg_vaddr(void *buf_priv) { struct vb2_dma_sg_buf *buf = buf_priv; if (!buf->vaddr) // 检查是否已映射 buf->vaddr = vm_map_ram(buf->pages, // 物理页数组 buf->num_pages, // 页数量 -1, // 自动选择映射位置 PAGE_KERNEL); // 内核页属性 return buf->vaddr; // 返回虚拟地址 } ``` 1. **输入参数** `buf_priv`指向`vb2_dma_sg_buf`结构体,包含: ```c struct vb2_dma_sg_buf { struct page **pages; // 物理页指针数组 int num_pages; // 分配的物理页数量 void *vaddr; // 内核虚拟地址(初始为NULL) // ...其他DMA相关字段 }; ``` 2. **延迟映射机制(Lazy Mapping)** 通过`if (!buf->vaddr)`条件实现**首次访问时才建立映射**,避免不必要的地址映射开销。 3. **物理到虚拟地址转换** 调用`vm_map_ram()`将**离散物理页**组装成**连续虚拟地址空间**: - `buf->pages`:指向物理页描述符(`struct page`)的数组 - `buf->num_pages`:需要映射的物理页数量 - `PAGE_KERNEL`:设置内存属性(可缓存、可写等) 4. **映射原理** 函数本质是**创建临时内核页表映射**,使CPU可通过返回的`vaddr`直接访问分散的物理内存区域。 #### 二、与RK3588的`dummy_buf->vaddr`关联 在RK3588的图形/视频处理程中,该函数与`dummy_buf->vaddr`获取存在直接技术关联: 1. **内存类型一致性** 引用[1]指出:RK3588的`dummy_buf`通常属于**V4L2_MEMORY_MMAP类型**,即通过`mmap()`映射的内核缓冲区。这与`vb2_dma_sg_vaddr`处理的缓冲区类型完全一致[^1]。 2. **地址获取机制** 两者均采用**按需映射模式**: - RK3588的`dummy_buf->vaddr`在首次访问时通过`dma_buf_vmap()`触发映射 - `vb2_dma_sg_vaddr()`在首次调用时通过`vm_map_ram()`触发映射 这种机制避免了提前映射未使用缓冲区的资源浪费。 3. **物理内存管理** RK3588的GPU/VPU硬件通常使用**非连续物理内存**(DMA散列表)。当`dummy_buf`通过`dequeueBuffer`获取时: ```mermaid graph LR A[dequeueBuffer] --> B[获取DMA-BUF句柄] B --> C{首次访问vaddr?} C -->|是| D[调用dma_buf_vmap] D --> E[dma_buf_ops->vmap回调] E --> F[vb2_dma_sg_vaddr] F --> G[vm_map_ram建立映射] ``` 4. **MMU协作** 如引用[3]所述,RK3588的`__setup_mmu()`初始化页表后,`vm_map_ram()`建立的映射才能被CPU通过虚拟地址正确访问[^3]。这解释了为何需要确保MMU已使能: $$ \text{虚拟地址} \xrightarrow{\text{MMU页表}} \text{物理地址} $$ #### 三、关键区别 | 特性 | `vb2_dma_sg_vaddr` | RK3588 `dummy_buf->vaddr` | |---------------------|----------------------------------|--------------------------------| | **调用上下文** | V4L2视频子系统内部 | Android图形栈/SurfaceFlinger | | **映射触发点** | 显式调用此函数时 | 首次访问缓冲区数据时 | | **底层实现** | 直接调用`vm_map_ram` | 通过DMA-BUF的`vmap`回调间接调用 | | **内存锁定** | 需预先锁定物理页(`dma_map_sg`) | 由BufferQueue生命周期管理 | #### 四、典型应用场景 在RK3588视频解码程中: ```c // 1. 从BufferQueue获取缓冲区 struct dummy_buffer *buf = dequeueBuffer(); // 2. 需要访问数据时触发映射 if (!buf->vaddr) { // 最终可能调用vb2_dma_sg_vaddr buf->vaddr = dma_buf_vmap(buf->dma_buf); } // 3. CPU直接操作虚拟地址 memcpy(buf->vaddr, data, size); // 4. 渲染完成后解除映射(延迟到释放缓冲区时) ``` ### 总结 `vb2_dma_sg_vaddr()`是**V4L2框架中将离散DMA物理内存映射为连续虚拟地址的核心函数**,而RK3588的`dummy_buf->vaddr`获取本质是通过DMA-BUF框架间接调用此类映射函数。两者在MMU页表管理(引用[3])和按需映射策略上高度一致,区别主要在于调用栈和内存管理上下文[^1][^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值