# v4l2_drm Python api说明

## 概述

`k230_v4l2_drm` 是嘉楠堪智 K230 平台上 V4L2 视频采集与 DRM 显示的 Python 绑定库。它将 Linux V4L2 (Video4Linux2) 摄像头采集和 DRM (Direct Rendering Manager) 显示输出整合为统一的流水线，支持：

- 多路摄像头采集：通过多个 context 同时采集多路视频
- 零拷贝 DRM 显示：V4L2 buffer 直接送 DRM 显示，无需额外拷贝
- OSD 叠加：在视频画面上叠加 ARGB8888/RGB888 图层（如检测框、文字）
- 旋转/翻转：硬件级旋转 (0°/90°/180°/270°) 和镜像翻转
- 裁剪/偏移：设置显示偏移和裁剪区域
- 后台显示线程：`display_start()` 在后台线程持续刷新显示

> 使用参考示例:  [py_demo/ai/yolo_v4l2_drm.py](https://github.com/kendryte/k230_linux_sdk/blob/dev/buildroot-overlay/package/python-k230/py_demo/ai/yolo_v4l2_drm.py) ,[py_demo/ai/face_detect.py](https://github.com/kendryte/k230_linux_sdk/blob/dev/buildroot-overlay/package/python-k230/py_demo/ai/face_detect.py)
>

## 完整 API 速查表

| 方法/属性     | 签名                                                         | 说明                   |
| ------------- | ------------------------------------------------------------ | ---------------------- |
| **构造**      | `V4l2Drm(context_num=1, osd=False)`                          | 创建实例               |
| **DRM初始化** | `drm_init(drm_id=0) → (w, h)`                                | 初始化显示, 返回分辨率 |
| **配置**      | `set_context(index, device, width, height, format, display)` | 配置摄像头参数         |
|               | `set_offset(index, x, y)`                                    | 设置显示偏移           |
|               | `set_crop(index, w, h, ox, oy)`                              | 设置裁剪区域           |
|               | `set_rotation(index, rotation)`                              | 设置旋转               |
|               | `set_flip(index, hflip, vflip)`                              | 设置翻转               |
|               | `set_buffer_num(index, num)`                                 | 设置缓冲区数量         |
| **流水线**    | `setup() → bool`                                             | 初始化流水线           |
|               | `display_start()`                                            | 启动后台显示线程       |
|               | `display_stop()`                                             | 停止显示线程           |
|               | `dump_start(index) → bool`                                   | 启动采集               |
|               | `dump_stop(index) → bool`                                    | 停止采集               |
|               | `dump_frame(index, timeout_ms) → bool`                       | 采集一帧               |
|               | `dump_release(index) → bool`                                 | 释放帧缓冲区           |
| **数据**      | `get_buffer_data(index) → ndarray`                           | 获取帧原始数据         |
|               | `get_buffer_array(index) → ndarray`                          | 获取帧数组 (带形状)    |
| **属性**      | `get_context_count() → int`                                  | 上下文数量             |
|               | `get_width(index) → int`                                     | 采集宽度               |
|               | `get_height(index) → int`                                    | 采集高度               |
|               | `get_device(index) → int`                                    | V4L2设备号             |
|               | `get_video_format(index) → int`                              | 格式FourCC             |
|               | `get_video_format_str(index) → str`                          | 格式字符串             |
|               | `get_offset_x(index) → int`                                  | X偏移                  |
|               | `get_offset_y(index) → int`                                  | Y偏移                  |
|               | `get_rotation(index) → int`                                  | 旋转值                 |
|               | `get_hflip(index) → int`                                     | 水平翻转               |
|               | `get_vflip(index) → int`                                     | 垂直翻转               |
|               | `get_frame_count(index) → int`                               | 帧计数                 |
|               | `set_frame_count(index, count)`                              | 设置帧计数             |
|               | `get_video_fd(index) → int`                                  | V4L2文件描述符         |
|               | `display_frame_count` (属性)                                 | 显示帧计数 (可读写)    |
| **OSD**       | `set_osd_format(fourcc)`                                     | 设置OSD格式            |
|               | `osd_update(img)`                                            | 更新OSD图像            |

## 模块常量

### 旋转枚举 (`Rotation`)

| 常量           | 值  | 说明                   |
| -------------- | --- | ---------------------- |
| `ROTATION_0`   | 0   | 不旋转                 |
| `ROTATION_90`  | 1   | 顺时针旋转 90°         |
| `ROTATION_180` | 2   | 旋转 180°              |
| `ROTATION_270` | 3   | 顺时针旋转 270°        |
| `REFLECT_X`    | 4   | 水平镜像 (沿 X 轴翻转) |
| `REFLECT_Y`    | 5   | 垂直镜像 (沿 Y 轴翻转) |

### 像素格式 (FourCC 字符串)

用于 `set_context()` 的 `format` 参数：

| 常量           | 值        | 说明                              |
| -------------- | --------- | --------------------------------- |
| `FORMAT_NV12`  | `b"NV12"` | YUV 4:2:0 半平面格式 (Y + 交错UV) |
| `FORMAT_NV21`  | `b"NV21"` | YUV 4:2:0 半平面格式 (Y + 交错VU) |
| `FORMAT_NV16`  | `b"NV16"` | YUV 4:2:2 半平面格式              |
| `FORMAT_NV61`  | `b"NV61"` | YUV 4:2:2 半平面格式              |
| `FORMAT_BGR24` | `b"BGR3"` | BGR 24位格式                      |
| `FORMAT_RGB24` | `b"RGB3"` | RGB 24位格式                      |
| `FORMAT_YUYV`  | `b"YUYV"` | YUYV 4:2:2 打包格式               |

> **注意**: `format` 参数接受字符串 (如 `"NV12"`)，内部会自动转换为 FourCC。K230 还支持 `"BG3P"` 格式 (BGR 平面格式，用于 AI 推理输入)。

### V4L2 像素格式常量 (整数)

| 常量                 | 说明                    |
| -------------------- | ----------------------- |
| `V4L2_PIX_FMT_NV12`  | V4L2 NV12 格式四字符码  |
| `V4L2_PIX_FMT_NV21`  | V4L2 NV21 格式四字符码  |
| `V4L2_PIX_FMT_NV16`  | V4L2 NV16 格式四字符码  |
| `V4L2_PIX_FMT_NV61`  | V4L2 NV61 格式四字符码  |
| `V4L2_PIX_FMT_BGR24` | V4L2 BGR24 格式四字符码 |
| `V4L2_PIX_FMT_RGB24` | V4L2 RGB24 格式四字符码 |
| `V4L2_PIX_FMT_YUYV`  | V4L2 YUYV 格式四字符码  |

### DRM 格式常量 (整数)

| 常量                  | 说明                                     |
| --------------------- | ---------------------------------------- |
| `DRM_FORMAT_NV12`     | DRM NV12 格式                            |
| `DRM_FORMAT_NV21`     | DRM NV21 格式                            |
| `DRM_FORMAT_NV16`     | DRM NV16 格式                            |
| `DRM_FORMAT_NV61`     | DRM NV61 格式                            |
| `DRM_FORMAT_BGR888`   | DRM BGR888 格式                          |
| `DRM_FORMAT_RGB888`   | DRM RGB888 格式                          |
| `DRM_FORMAT_YUYV`     | DRM YUYV 格式                            |
| `DRM_FORMAT_ARGB8888` | DRM ARGB8888 格式 (用于 OSD)             |
| `DRM_FORMAT_XRGB8888` | DRM XRGB8888 格式 (用于 OSD, 无透明通道) |

---

## 类: `V4l2Drm`

### 构造函数

```python
V4l2Drm(context_num=1, osd=False)
```

| 参数          | 类型   | 默认值  | 说明                                |
| ------------- | ------ | ------- | ----------------------------------- |
| `context_num` | `int`  | `1`     | 视频采集上下文数量 (对应摄像头路数) |
| `osd`         | `bool` | `False` | 是否启用 OSD 叠加图层               |

**示例:**

```python
# 单路摄像头, 无OSD
v4l2 = V4l2Drm(context_num=1)

# 双路摄像头 (如: 一路显示 + 一路AI推理), 启用OSD
v4l2 = V4l2Drm(context_num=2, osd=True)
```

---

### 初始化与配置

#### `drm_init(drm_id=0)`

初始化 DRM 显示设备。

| 参数     | 类型  | 默认值 | 说明                                 |
| -------- | ----- | ------ | ------------------------------------ |
| `drm_id` | `int` | `0`    | DRM 设备编号 (对应 `/dev/dri/cardN`) |

**返回值**: `tuple(int, int)` — 成功返回 `(width, height)` 屏幕分辨率；失败返回 `(-1, -1)`

```python
width, height = v4l2.drm_init(drm_id=0)
if width == -1:
    raise RuntimeError("DRM init failed")
```

#### `set_context(index, device, width, height, format, display)`

配置指定上下文的摄像头参数。

| 参数      | 类型   | 默认值   | 说明                                       |
| --------- | ------ | -------- | ------------------------------------------ |
| `index`   | `int`  | `0`      | 上下文编号 (0 ~ context_num-1)             |
| `device`  | `int`  | `1`      | V4L2 设备编号 (对应 `/dev/videoN`)         |
| `width`   | `int`  | `640`    | 采集分辨率宽度                             |
| `height`  | `int`  | `480`    | 采集分辨率高度                             |
| `format`  | `str`  | `"NV12"` | 像素格式 (如 `"NV12"`, `"BG3P"`, `"YUYV"`) |
| `display` | `bool` | `True`   | 是否将该路视频输出到屏幕显示               |

```python
# Context 0: 摄像头1, 1080p, NV12格式, 用于显示
v4l2.set_context(index=0, device=1, width=1920, height=1080, format="NV12", display=True)

# Context 1: 摄像头2, 720p, BG3P格式, 仅用于AI推理, 不显示
v4l2.set_context(index=1, device=2, width=1280, height=720, format="BG3P", display=False)
```

#### `set_offset(index, x, y)`

设置指定上下文在屏幕上的显示偏移。

| 参数    | 类型  | 默认值 | 说明          |
| ------- | ----- | ------ | ------------- |
| `index` | `int` | `0`    | 上下文编号    |
| `x`     | `int` | `0`    | X 偏移 (像素) |
| `y`     | `int` | `0`    | Y 偏移 (像素) |

```python
# 将context 0的画面偏移到 (100, 50) 位置
v4l2.set_offset(index=0, x=100, y=50)
```

#### `set_crop(index, w, h, ox, oy)`

设置指定上下文的裁剪区域。

| 参数    | 类型  | 默认值 | 说明                  |
| ------- | ----- | ------ | --------------------- |
| `index` | `int` | `0`    | 上下文编号            |
| `w`     | `int` | `0`    | 裁剪宽度 (0 = 不裁剪) |
| `h`     | `int` | `0`    | 裁剪高度 (0 = 不裁剪) |
| `ox`    | `int` | `0`    | 裁剪区域 X 偏移       |
| `oy`    | `int` | `0`    | 裁剪区域 Y 偏移       |

```python
# 裁剪出 640x480 区域, 起始偏移 (100, 100)
v4l2.set_crop(index=0, w=640, h=480, ox=100, oy=100)
```

#### `set_rotation(index, rotation)`

设置指定上下文的旋转模式。

| 参数       | 类型  | 默认值 | 说明                             |
| ---------- | ----- | ------ | -------------------------------- |
| `index`    | `int` | `0`    | 上下文编号                       |
| `rotation` | `int` | `0`    | 旋转值, 使用 `Rotation` 枚举常量 |

```python
from k230_v4l2_drm import V4l2Drm, ROTATION_90, ROTATION_0

# 根据屏幕方向自动选择旋转
if width > height:
    v4l2.set_rotation(0, ROTATION_0)   # 横屏, 不旋转
else:
    v4l2.set_rotation(0, ROTATION_90)  # 竖屏, 旋转90度
```

#### `set_flip(index, hflip, vflip)`

设置指定上下文的镜像翻转。

| 参数    | 类型  | 默认值 | 说明                      |
| ------- | ----- | ------ | ------------------------- |
| `index` | `int` | `0`    | 上下文编号                |
| `hflip` | `int` | `0`    | 水平翻转 (0=关闭, 1=开启) |
| `vflip` | `int` | `0`    | 垂直翻转 (0=关闭, 1=开启) |

```python
# 水平镜像 (左右翻转)
v4l2.set_flip(index=0, hflip=1, vflip=0)
```

#### `set_buffer_num(index, num)`

设置指定上下文的 V4L2 缓冲区数量。

| 参数    | 类型  | 默认值 | 说明                        |
| ------- | ----- | ------ | --------------------------- |
| `index` | `int` | `0`    | 上下文编号                  |
| `num`   | `int` | `4`    | 缓冲区数量 (最少 3, 推荐 4) |

```python
v4l2.set_buffer_num(index=0, num=4)
```

---

### 流水线控制

#### `setup()`

初始化并启动 V4L2-DRM 流水线。在所有 `set_context` / `set_rotation` 等配置完成后调用。

**返回值**: `bool` — 成功返回 `True`，失败返回 `False`

```python
if not v4l2.setup():
    raise RuntimeError("V4L2-DRM setup failed!")
```

#### `display_start()`

在后台线程启动显示刷新循环。调用后显示线程持续运行，将 V4L2 buffer 提交到 DRM 显示。

```python
v4l2.display_start()
```

#### `display_stop()`

停止显示刷新循环并等待后台线程结束。

```python
v4l2.display_stop()
```

#### `dump_start(index)`

启动指定上下文的视频采集 (开始 V4L2 流式采集)。

| 参数    | 类型  | 默认值 | 说明       |
| ------- | ----- | ------ | ---------- |
| `index` | `int` | `0`    | 上下文编号 |

**返回值**: `bool`

```python
v4l2.dump_start(index=0)
```

#### `dump_stop(index)`

停止指定上下文的视频采集。

| 参数    | 类型  | 默认值 | 说明       |
| ------- | ----- | ------ | ---------- |
| `index` | `int` | `0`    | 上下文编号 |

**返回值**: `bool`

```python
v4l2.dump_stop(index=0)
```

#### `dump_frame(index, timeout_ms)`

从指定上下文采集一帧。阻塞等待直到有帧可用或超时。

| 参数         | 类型  | 默认值 | 说明            |
| ------------ | ----- | ------ | --------------- |
| `index`      | `int` | `0`    | 上下文编号      |
| `timeout_ms` | `int` | `1000` | 超时时间 (毫秒) |

**返回值**: `bool` — 成功采集返回 `True`，超时或失败返回 `False`

```python
if v4l2.dump_frame(index=0, timeout_ms=1000):
    # 成功获取一帧
    frame_data = v4l2.get_buffer_data(index=0)
    # ... 处理帧数据 ...
    v4l2.dump_release(index=0)  # 必须释放!
else:
    print("Frame capture timeout")
```

> **重要**: `dump_frame` 成功后必须调用 `dump_release` 释放缓冲区，否则后续帧无法入队。

#### `dump_release(index)`

释放指定上下文已采集的帧缓冲区，使其可被 V4L2 重新使用。

| 参数    | 类型  | 默认值 | 说明       |
| ------- | ----- | ------ | ---------- |
| `index` | `int` | `0`    | 上下文编号 |

**返回值**: `bool`

---

### 数据访问

#### `get_buffer_data(index)`

获取指定上下文当前帧的原始数据，返回 numpy 数组。

| 参数    | 类型  | 默认值 | 说明       |
| ------- | ----- | ------ | ---------- |
| `index` | `int` | `0`    | 上下文编号 |

**返回值**: `numpy.ndarray` — 一维 uint8 数组，包含帧的原始像素数据

```python
raw_data = v4l2.get_buffer_data(index=0)
# raw_data.shape = (frame_size,), dtype = uint8
```

#### `get_buffer_array(index)`

获取指定上下文当前帧数据，返回具有形状信息的 numpy 数组。

| 参数    | 类型  | 默认值 | 说明       |
| ------- | ----- | ------ | ---------- |
| `index` | `int` | `0`    | 上下文编号 |

**返回值**: `numpy.ndarray` — 带形状的 numpy 数组

- 对于 **NV12** 格式: shape = `(height * 3 // 2, width)` 或类似
- 对于 **BG3P** 格式 (BGR 平面): shape = `(3, height, width)` 即 NCHW 排列
- 对于 **YUYV** 格式: shape = `(height, width, 2)`

```python
frame = v4l2.get_buffer_array(index=1)
# 对于BG3P格式: frame.shape = (3, 720, 1280), 可直接用于AI推理
```

---

### 属性查询

| 方法                                | 返回类型 | 说明                                                  |
| ----------------------------------- | -------- | ----------------------------------------------------- |
| `get_context_count()`               | `int`    | 上下文总数                                            |
| `get_width(index=0)`                | `int`    | 指定上下文的采集宽度                                  |
| `get_height(index=0)`               | `int`    | 指定上下文的采集高度                                  |
| `get_device(index=0)`               | `int`    | 指定上下文的 V4L2 设备编号                            |
| `get_video_format(index=0)`         | `int`    | 指定上下文的 V4L2 格式 (整数 FourCC)                  |
| `get_video_format_str(index=0)`     | `str`    | 指定上下文的格式字符串 (如 `"NV12"`)                  |
| `get_offset_x(index=0)`             | `int`    | 指定上下文的 X 偏移                                   |
| `get_offset_y(index=0)`             | `int`    | 指定上下文的 Y 偏移                                   |
| `get_rotation(index=0)`             | `int`    | 指定上下文的旋转值                                    |
| `get_hflip(index=0)`                | `int`    | 指定上下文的水平翻转状态                              |
| `get_vflip(index=0)`                | `int`    | 指定上下文的垂直翻转状态                              |
| `get_frame_count(index=0)`          | `int`    | 指定上下文的帧计数                                    |
| `set_frame_count(index=0, count=0)` | `void`   | 设置指定上下文的帧计数                                |
| `get_video_fd(index=0)`             | `int`    | 指定上下文的 V4L2 文件描述符 (可用于 `select`/`poll`) |

---

### 显示帧计数属性

#### `display_frame_count` (属性)

当前显示帧计数器，可读写。

```python
# 读取
count = v4l2.display_frame_count

# 写入
v4l2.display_frame_count = 0
```

---

### OSD (On-Screen Display) 叠加

OSD 功能允许在视频画面上叠加透明图层，常用于绘制 AI 检测框、文字等。

> **前提**: 构造 `V4l2Drm` 时必须设置 `osd=True`

#### `set_osd_format(fourcc)`

设置 OSD 图层格式。

| 参数     | 类型  | 说明                                                                |
| -------- | ----- | ------------------------------------------------------------------- |
| `fourcc` | `int` | DRM 格式常量，通常为 `DRM_FORMAT_ARGB8888` 或 `DRM_FORMAT_XRGB8888` |

```python
from k230_v4l2_drm import V4l2Drm, DRM_FORMAT_ARGB8888

v4l2 = V4l2Drm(context_num=1, osd=True)
v4l2.drm_init()
# ...
v4l2.set_osd_format(DRM_FORMAT_ARGB8888)
```

#### `osd_update(img)`

更新 OSD 图层内容。将 numpy 数组图像数据写入 OSD 平面。

| 参数  | 类型            | 说明         |
| ----- | --------------- | ------------ |
| `img` | `numpy.ndarray` | OSD 图像数据 |

**图像要求:**

- 格式为 ARGB8888 时: shape = `(height, width, 4)`, dtype = `uint8`
- 格式为 RGB888 时: shape = `(height, width, 3)`, dtype = `uint8`
- 尺寸应与显示分辨率匹配
- Alpha 通道: 0 = 完全透明, 255 = 完全不透明

```python
import numpy as np

# 创建全透明OSD图像 (与显示同尺寸)
osd_img = np.zeros((display_height, display_width, 4), dtype=np.uint8)

# 绘制红色矩形框 (ARGB格式: B, G, R, A)
osd_img[100:200, 100:300] = [0, 0, 255, 255]  # 红色不透明

# 绘制绿色文字区域
osd_img[10:50, 10:200] = [0, 255, 0, 255]  # 绿色不透明

# 更新OSD
v4l2.osd_update(osd_img)
```

> **注意**: `osd_update` 每次调用会完整替换 OSD 图层内容。如需叠加多个元素，应先在 numpy 数组上绘制完成后一次性提交。

---

---

---
