|
@@ -1,6 +1,22 @@
|
|
|
---
|
|
---
|
|
|
title: 摄像头、串口与音频应用编程
|
|
title: 摄像头、串口与音频应用编程
|
|
|
-tags: [嵌入式Linux, Linux应用编程, V4L2, 摄像头, 视频采集, 串口, UART, termios, ALSA, alsa-lib, PCM, WAV, 音频, IMX6ULL]
|
|
|
|
|
|
|
+tags:
|
|
|
|
|
+ [
|
|
|
|
|
+ 嵌入式Linux,
|
|
|
|
|
+ Linux应用编程,
|
|
|
|
|
+ V4L2,
|
|
|
|
|
+ 摄像头,
|
|
|
|
|
+ 视频采集,
|
|
|
|
|
+ 串口,
|
|
|
|
|
+ UART,
|
|
|
|
|
+ termios,
|
|
|
|
|
+ ALSA,
|
|
|
|
|
+ alsa-lib,
|
|
|
|
|
+ PCM,
|
|
|
|
|
+ WAV,
|
|
|
|
|
+ 音频,
|
|
|
|
|
+ IMX6ULL,
|
|
|
|
|
+ ]
|
|
|
created: 2026-09-18
|
|
created: 2026-09-18
|
|
|
updated: 2026-09-18
|
|
updated: 2026-09-18
|
|
|
pdf_ref: "《I.MX6U嵌入式Linux C应用编程指南V1.6》第二十五章 V4L2摄像头应用编程、第二十六章 串口应用编程、第二十八章 音频应用编程"
|
|
pdf_ref: "《I.MX6U嵌入式Linux C应用编程指南V1.6》第二十五章 V4L2摄像头应用编程、第二十六章 串口应用编程、第二十八章 音频应用编程"
|
|
@@ -53,23 +69,23 @@ flowchart TB
|
|
|
|
|
|
|
|
所有指令定义在头文件 `<linux/videodev2.h>` 中,以宏 `VIDIOC_XXX` 形式提供,每个宏还携带一个 `struct` 类型——即调用 `ioctl()` 时第三个参数的类型,传入该类型变量的指针。
|
|
所有指令定义在头文件 `<linux/videodev2.h>` 中,以宏 `VIDIOC_XXX` 形式提供,每个宏还携带一个 `struct` 类型——即调用 `ioctl()` 时第三个参数的类型,传入该类型变量的指针。
|
|
|
|
|
|
|
|
-| V4L2 指令 | 描述 |
|
|
|
|
|
-| --------- | ---- |
|
|
|
|
|
-| `VIDIOC_QUERYCAP` | 查询设备的属性/能力/功能 |
|
|
|
|
|
-| `VIDIOC_ENUM_FMT` | 枚举设备支持的像素格式 |
|
|
|
|
|
-| `VIDIOC_G_FMT` | 获取设备当前的帧格式信息 |
|
|
|
|
|
-| `VIDIOC_S_FMT` | 设置帧格式信息 |
|
|
|
|
|
-| `VIDIOC_REQBUFS` | 申请帧缓冲 |
|
|
|
|
|
-| `VIDIOC_QUERYBUF` | 查询帧缓冲 |
|
|
|
|
|
-| `VIDIOC_QBUF` | 帧缓冲入队操作 |
|
|
|
|
|
-| `VIDIOC_DQBUF` | 帧缓冲出队操作 |
|
|
|
|
|
-| `VIDIOC_STREAMON` | 开启视频采集 |
|
|
|
|
|
-| `VIDIOC_STREAMOFF` | 关闭视频采集 |
|
|
|
|
|
-| `VIDIOC_G_PARM` | 获取设备的流类型参数 |
|
|
|
|
|
-| `VIDIOC_S_PARM` | 设置流类型参数(帧率) |
|
|
|
|
|
-| `VIDIOC_TRY_FMT` | 尝试设置帧格式,用于判断设备是否支持该格式 |
|
|
|
|
|
-| `VIDIOC_ENUM_FRAMESIZES` | 枚举设备支持的视频采集分辨率 |
|
|
|
|
|
-| `VIDIOC_ENUM_FRAMEINTERVALS` | 枚举设备支持的视频采集帧率 |
|
|
|
|
|
|
|
+| V4L2 指令 | 描述 |
|
|
|
|
|
+| ---------------------------- | ------------------------------------------ |
|
|
|
|
|
+| `VIDIOC_QUERYCAP` | 查询设备的属性/能力/功能 |
|
|
|
|
|
+| `VIDIOC_ENUM_FMT` | 枚举设备支持的像素格式 |
|
|
|
|
|
+| `VIDIOC_G_FMT` | 获取设备当前的帧格式信息 |
|
|
|
|
|
+| `VIDIOC_S_FMT` | 设置帧格式信息 |
|
|
|
|
|
+| `VIDIOC_REQBUFS` | 申请帧缓冲 |
|
|
|
|
|
+| `VIDIOC_QUERYBUF` | 查询帧缓冲 |
|
|
|
|
|
+| `VIDIOC_QBUF` | 帧缓冲入队操作 |
|
|
|
|
|
+| `VIDIOC_DQBUF` | 帧缓冲出队操作 |
|
|
|
|
|
+| `VIDIOC_STREAMON` | 开启视频采集 |
|
|
|
|
|
+| `VIDIOC_STREAMOFF` | 关闭视频采集 |
|
|
|
|
|
+| `VIDIOC_G_PARM` | 获取设备的流类型参数 |
|
|
|
|
|
+| `VIDIOC_S_PARM` | 设置流类型参数(帧率) |
|
|
|
|
|
+| `VIDIOC_TRY_FMT` | 尝试设置帧格式,用于判断设备是否支持该格式 |
|
|
|
|
|
+| `VIDIOC_ENUM_FRAMESIZES` | 枚举设备支持的视频采集分辨率 |
|
|
|
|
|
+| `VIDIOC_ENUM_FRAMEINTERVALS` | 枚举设备支持的视频采集帧率 |
|
|
|
|
|
|
|
|
## 1.4 关键数据结构
|
|
## 1.4 关键数据结构
|
|
|
|
|
|
|
@@ -89,13 +105,13 @@ struct v4l2_capability {
|
|
|
|
|
|
|
|
重点是 `capabilities`,它描述设备能力,可为多个值的位或。摄像头设备必须包含 `V4L2_CAP_VIDEO_CAPTURE`(0x00000001,视频采集)。其它常用值:
|
|
重点是 `capabilities`,它描述设备能力,可为多个值的位或。摄像头设备必须包含 `V4L2_CAP_VIDEO_CAPTURE`(0x00000001,视频采集)。其它常用值:
|
|
|
|
|
|
|
|
-| 能力宏 | 值 | 含义 |
|
|
|
|
|
-| ------ | -- | ---- |
|
|
|
|
|
-| `V4L2_CAP_VIDEO_CAPTURE` | 0x00000001 | 是视频采集设备 |
|
|
|
|
|
-| `V4L2_CAP_VIDEO_OUTPUT` | 0x00000002 | 是视频输出设备 |
|
|
|
|
|
-| `V4L2_CAP_VIDEO_OVERLAY` | 0x00000004 | 支持视频叠加 |
|
|
|
|
|
-| `V4L2_CAP_READWRITE` | 0x01000000 | 支持 read/write 方式读写 |
|
|
|
|
|
-| `V4L2_CAP_STREAMING` | 0x04000000 | 支持 streaming I/O 方式 |
|
|
|
|
|
|
|
+| 能力宏 | 值 | 含义 |
|
|
|
|
|
+| ------------------------ | ---------- | ------------------------ |
|
|
|
|
|
+| `V4L2_CAP_VIDEO_CAPTURE` | 0x00000001 | 是视频采集设备 |
|
|
|
|
|
+| `V4L2_CAP_VIDEO_OUTPUT` | 0x00000002 | 是视频输出设备 |
|
|
|
|
|
+| `V4L2_CAP_VIDEO_OVERLAY` | 0x00000004 | 支持视频叠加 |
|
|
|
|
|
+| `V4L2_CAP_READWRITE` | 0x01000000 | 支持 read/write 方式读写 |
|
|
|
|
|
+| `V4L2_CAP_STREAMING` | 0x04000000 | 支持 streaming I/O 方式 |
|
|
|
|
|
|
|
|
### 1.4.2 struct v4l2_fmtdesc / frmsizeenum / frmivalenum
|
|
### 1.4.2 struct v4l2_fmtdesc / frmsizeenum / frmivalenum
|
|
|
|
|
|
|
@@ -150,21 +166,21 @@ struct v4l2_fract {
|
|
|
|
|
|
|
|
常用像素格式(由 `v4l2_fourcc` 宏合成 32 位数据):
|
|
常用像素格式(由 `v4l2_fourcc` 宏合成 32 位数据):
|
|
|
|
|
|
|
|
-| 像素格式宏 | fourcc | 含义 |
|
|
|
|
|
-| ---------- | ------ | ---- |
|
|
|
|
|
|
|
+| 像素格式宏 | fourcc | 含义 |
|
|
|
|
|
+| --------------------- | ------ | --------------- |
|
|
|
| `V4L2_PIX_FMT_RGB565` | 'RGBP' | 16 位 RGB 5-6-5 |
|
|
| `V4L2_PIX_FMT_RGB565` | 'RGBP' | 16 位 RGB 5-6-5 |
|
|
|
| `V4L2_PIX_FMT_RGB555` | 'RGBO' | 16 位 RGB 5-5-5 |
|
|
| `V4L2_PIX_FMT_RGB555` | 'RGBO' | 16 位 RGB 5-5-5 |
|
|
|
-| `V4L2_PIX_FMT_YUYV` | 'YUYV' | 16 位 YUV 4:2:2 |
|
|
|
|
|
-| `V4L2_PIX_FMT_MJPEG` | 'MJPG' | Motion-JPEG |
|
|
|
|
|
-| `V4L2_PIX_FMT_GREY` | 'GREY' | 8 位灰度 |
|
|
|
|
|
|
|
+| `V4L2_PIX_FMT_YUYV` | 'YUYV' | 16 位 YUV 4:2:2 |
|
|
|
|
|
+| `V4L2_PIX_FMT_MJPEG` | 'MJPG' | Motion-JPEG |
|
|
|
|
|
+| `V4L2_PIX_FMT_GREY` | 'GREY' | 8 位灰度 |
|
|
|
|
|
|
|
|
`v4l2_buf_type` 常用取值:
|
|
`v4l2_buf_type` 常用取值:
|
|
|
|
|
|
|
|
-| 取值 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `V4L2_BUF_TYPE_VIDEO_CAPTURE`(1) | 视频采集 |
|
|
|
|
|
-| `V4L2_BUF_TYPE_VIDEO_OUTPUT`(2) | 视频输出 |
|
|
|
|
|
-| `V4L2_BUF_TYPE_VIDEO_OVERLAY`(3) | 视频叠加 |
|
|
|
|
|
|
|
+| 取值 | 含义 |
|
|
|
|
|
+| --------------------------------------- | -------------- |
|
|
|
|
|
+| `V4L2_BUF_TYPE_VIDEO_CAPTURE`(1) | 视频采集 |
|
|
|
|
|
+| `V4L2_BUF_TYPE_VIDEO_OUTPUT`(2) | 视频输出 |
|
|
|
|
|
+| `V4L2_BUF_TYPE_VIDEO_OVERLAY`(3) | 视频叠加 |
|
|
|
| `V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE`(9) | 多平面视频采集 |
|
|
| `V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE`(9) | 多平面视频采集 |
|
|
|
|
|
|
|
|
### 1.4.3 struct v4l2_format / v4l2_pix_format
|
|
### 1.4.3 struct v4l2_format / v4l2_pix_format
|
|
@@ -221,9 +237,9 @@ struct v4l2_captureparm {
|
|
|
|
|
|
|
|
`capability` 字段标志:
|
|
`capability` 字段标志:
|
|
|
|
|
|
|
|
-| 标志 | 值 | 含义 |
|
|
|
|
|
-| ---- | -- | ---- |
|
|
|
|
|
-| `V4L2_MODE_HIGHQUALITY` | 0x0001 | 高品质成像模式 |
|
|
|
|
|
|
|
+| 标志 | 值 | 含义 |
|
|
|
|
|
+| ----------------------- | ------ | ---------------------------- |
|
|
|
|
|
+| `V4L2_MODE_HIGHQUALITY` | 0x0001 | 高品质成像模式 |
|
|
|
| `V4L2_CAP_TIMEPERFRAME` | 0x1000 | 支持设置 `timeperframe` 字段 |
|
|
| `V4L2_CAP_TIMEPERFRAME` | 0x1000 | 支持设置 `timeperframe` 字段 |
|
|
|
|
|
|
|
|
只有 `capability` 包含 `V4L2_CAP_TIMEPERFRAME` 时,应用层才能通过 `VIDIOC_S_PARM` 设置帧率。
|
|
只有 `capability` 包含 `V4L2_CAP_TIMEPERFRAME` 时,应用层才能通过 `VIDIOC_S_PARM` 设置帧率。
|
|
@@ -262,12 +278,12 @@ struct v4l2_buffer {
|
|
|
|
|
|
|
|
`enum v4l2_memory`:
|
|
`enum v4l2_memory`:
|
|
|
|
|
|
|
|
-| 取值 | 值 | 含义 |
|
|
|
|
|
-| ---- | -- | ---- |
|
|
|
|
|
-| `V4L2_MEMORY_MMAP` | 1 | 内存映射(最常用) |
|
|
|
|
|
-| `V4L2_MEMORY_USERPTR` | 2 | 用户指针 |
|
|
|
|
|
-| `V4L2_MEMORY_OVERLAY` | 3 | 叠加 |
|
|
|
|
|
-| `V4L2_MEMORY_DMABUF` | 4 | DMA 缓冲 |
|
|
|
|
|
|
|
+| 取值 | 值 | 含义 |
|
|
|
|
|
+| --------------------- | --- | ------------------ |
|
|
|
|
|
+| `V4L2_MEMORY_MMAP` | 1 | 内存映射(最常用) |
|
|
|
|
|
+| `V4L2_MEMORY_USERPTR` | 2 | 用户指针 |
|
|
|
|
|
+| `V4L2_MEMORY_OVERLAY` | 3 | 叠加 |
|
|
|
|
|
+| `V4L2_MEMORY_DMABUF` | 4 | DMA 缓冲 |
|
|
|
|
|
|
|
|
`length` 是帧缓冲长度,`m.offset` 是帧缓冲在内核申请的那块大内存中的偏移量;`VIDIOC_REQBUFS` 时内核申请"数量 × 单个大小"的连续内存,每个帧缓冲对应其中一段。
|
|
`length` 是帧缓冲长度,`m.offset` 是帧缓冲在内核申请的那块大内存中的偏移量;`VIDIOC_REQBUFS` 时内核申请"数量 × 单个大小"的连续内存,每个帧缓冲对应其中一段。
|
|
|
|
|
|
|
@@ -704,11 +720,11 @@ static void yuyv_to_rgb565(const unsigned char *src, unsigned short *dst, int pi
|
|
|
|
|
|
|
|
串口在 Linux 中就是一个**终端(Terminal)**。终端是"处理主机输入、输出的一套设备",能接受输入、能显示输出就是终端。终端分类:
|
|
串口在 Linux 中就是一个**终端(Terminal)**。终端是"处理主机输入、输出的一套设备",能接受输入、能显示输出就是终端。终端分类:
|
|
|
|
|
|
|
|
-| 类型 | 说明 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| 本地终端 | 连接本机键盘显示器,如 `/dev/tty1`~`/dev/tty63` |
|
|
|
|
|
-| 串口远程终端 | 开发板最常见,通过串口线连接 PC,运行 putty/MobaXterm/SecureCRT |
|
|
|
|
|
-| 网络远程终端(伪终端) | ssh/Telnet 登录,节点在 `/dev/pts/X` |
|
|
|
|
|
|
|
+| 类型 | 说明 |
|
|
|
|
|
+| ---------------------- | --------------------------------------------------------------- |
|
|
|
|
|
+| 本地终端 | 连接本机键盘显示器,如 `/dev/tty1`~`/dev/tty63` |
|
|
|
|
|
+| 串口远程终端 | 开发板最常见,通过串口线连接 PC,运行 putty/MobaXterm/SecureCRT |
|
|
|
|
|
+| 网络远程终端(伪终端) | ssh/Telnet 登录,节点在 `/dev/pts/X` |
|
|
|
|
|
|
|
|
设备节点:
|
|
设备节点:
|
|
|
|
|
|
|
@@ -743,53 +759,53 @@ struct termios {
|
|
|
|
|
|
|
|
控制输入数据(驱动从串口/键盘收到的字符)在交给应用前的处理方式。
|
|
控制输入数据(驱动从串口/键盘收到的字符)在交给应用前的处理方式。
|
|
|
|
|
|
|
|
-| 标志 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `IGNBRK` | 忽略输入终止条件 |
|
|
|
|
|
-| `BRKINT` | 检测到输入终止条件时发送 `SIGINT` 信号 |
|
|
|
|
|
-| `IGNPAR` | 忽略帧错误和奇偶校验错误 |
|
|
|
|
|
-| `PARMRK` | 对奇偶校验错误做出标记 |
|
|
|
|
|
-| `INPCK` | 对接收到的数据执行奇偶校验 |
|
|
|
|
|
|
|
+| 标志 | 含义 |
|
|
|
|
|
+| -------- | ------------------------------------------- |
|
|
|
|
|
+| `IGNBRK` | 忽略输入终止条件 |
|
|
|
|
|
+| `BRKINT` | 检测到输入终止条件时发送 `SIGINT` 信号 |
|
|
|
|
|
+| `IGNPAR` | 忽略帧错误和奇偶校验错误 |
|
|
|
|
|
+| `PARMRK` | 对奇偶校验错误做出标记 |
|
|
|
|
|
+| `INPCK` | 对接收到的数据执行奇偶校验 |
|
|
|
| `ISTRIP` | 将所有接收数据裁剪为 7 比特位(去掉第八位) |
|
|
| `ISTRIP` | 将所有接收数据裁剪为 7 比特位(去掉第八位) |
|
|
|
-| `INLCR` | 将接收到的 NL(换行符)转换为 CR(回车符) |
|
|
|
|
|
-| `IGNCR` | 忽略接收到的 CR(回车符) |
|
|
|
|
|
-| `ICRNL` | 将接收到的 CR(回车符)转换为 NL(换行符) |
|
|
|
|
|
-| `IUCLC` | 将接收到的大写字符映射为小写字符 |
|
|
|
|
|
-| `IXON` | 启动输出软件流控 |
|
|
|
|
|
-| `IXOFF` | 启动输入软件流控 |
|
|
|
|
|
|
|
+| `INLCR` | 将接收到的 NL(换行符)转换为 CR(回车符) |
|
|
|
|
|
+| `IGNCR` | 忽略接收到的 CR(回车符) |
|
|
|
|
|
+| `ICRNL` | 将接收到的 CR(回车符)转换为 NL(换行符) |
|
|
|
|
|
+| `IUCLC` | 将接收到的大写字符映射为小写字符 |
|
|
|
|
|
+| `IXON` | 启动输出软件流控 |
|
|
|
|
|
+| `IXOFF` | 启动输入软件流控 |
|
|
|
|
|
|
|
|
### 2.2.2 输出模式 c_oflag
|
|
### 2.2.2 输出模式 c_oflag
|
|
|
|
|
|
|
|
控制输出字符在传递到串口/屏幕前的处理方式。
|
|
控制输出字符在传递到串口/屏幕前的处理方式。
|
|
|
|
|
|
|
|
-| 标志 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `OPOST` | 启用输出处理功能;不设置该标志则其它标志都被忽略 |
|
|
|
|
|
-| `OLCUC` | 将输出字符中的大写字符转换成小写字符 |
|
|
|
|
|
-| `ONLCR` | 将输出中的换行符 NL(`\n`)转换成回车符 CR(`\r`) |
|
|
|
|
|
-| `OCRNL` | 将输出中的回车符 CR(`\r`)转换成换行符 NL(`\n`) |
|
|
|
|
|
-| `ONOCR` | 在第 0 列不输出回车符 CR |
|
|
|
|
|
-| `ONLRET` | 不输出回车符 |
|
|
|
|
|
-| `OFILL` | 发送填充字符以提供延时 |
|
|
|
|
|
-| `OFDEL` | 若设置该标志,填充字符为 DEL 字符,否则为 NULL 字符 |
|
|
|
|
|
|
|
+| 标志 | 含义 |
|
|
|
|
|
+| -------- | --------------------------------------------------- |
|
|
|
|
|
+| `OPOST` | 启用输出处理功能;不设置该标志则其它标志都被忽略 |
|
|
|
|
|
+| `OLCUC` | 将输出字符中的大写字符转换成小写字符 |
|
|
|
|
|
+| `ONLCR` | 将输出中的换行符 NL(`\n`)转换成回车符 CR(`\r`) |
|
|
|
|
|
+| `OCRNL` | 将输出中的回车符 CR(`\r`)转换成换行符 NL(`\n`) |
|
|
|
|
|
+| `ONOCR` | 在第 0 列不输出回车符 CR |
|
|
|
|
|
+| `ONLRET` | 不输出回车符 |
|
|
|
|
|
+| `OFILL` | 发送填充字符以提供延时 |
|
|
|
|
|
+| `OFDEL` | 若设置该标志,填充字符为 DEL 字符,否则为 NULL 字符 |
|
|
|
|
|
|
|
|
### 2.2.3 控制模式 c_cflag
|
|
### 2.2.3 控制模式 c_cflag
|
|
|
|
|
|
|
|
控制终端硬件特性,对串口最重要:波特率、数据位、校验位、停止位等。
|
|
控制终端硬件特性,对串口最重要:波特率、数据位、校验位、停止位等。
|
|
|
|
|
|
|
|
-| 标志 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `CBAUD` | 波特率的位掩码 |
|
|
|
|
|
-| `B0`/`B1200`/`B1800`/`B2400`/`B4800`/`B9600`/`B19200`/`B38400`/`B57600`/`B115200`/`B230400`/`B460800`/`B500000`/`B576000`/`B921600`/`B1000000`/`B1152000`/`B1500000`/`B2000000`/`B2500000`/`B3000000` | 对应波特率 |
|
|
|
|
|
-| `CSIZE` | 数据位的位掩码 |
|
|
|
|
|
-| `CS5`/`CS6`/`CS7`/`CS8` | 5/6/7/8 个数据位 |
|
|
|
|
|
-| `CSTOPB` | 2 个停止位;不设置则默认 1 个停止位 |
|
|
|
|
|
-| `CREAD` | 接收使能 |
|
|
|
|
|
-| `PARENB` | 使能奇偶校验 |
|
|
|
|
|
-| `PARODD` | 使用奇校验,而不是偶校验 |
|
|
|
|
|
-| `HUPCL` | 关闭时挂断调制解调器 |
|
|
|
|
|
-| `CLOCAL` | 忽略调制解调器控制线 |
|
|
|
|
|
-| `CRTSCTS` | 使能硬件流控 |
|
|
|
|
|
|
|
+| 标志 | 含义 |
|
|
|
|
|
+| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
|
|
|
+| `CBAUD` | 波特率的位掩码 |
|
|
|
|
|
+| `B0`/`B1200`/`B1800`/`B2400`/`B4800`/`B9600`/`B19200`/`B38400`/`B57600`/`B115200`/`B230400`/`B460800`/`B500000`/`B576000`/`B921600`/`B1000000`/`B1152000`/`B1500000`/`B2000000`/`B2500000`/`B3000000` | 对应波特率 |
|
|
|
|
|
+| `CSIZE` | 数据位的位掩码 |
|
|
|
|
|
+| `CS5`/`CS6`/`CS7`/`CS8` | 5/6/7/8 个数据位 |
|
|
|
|
|
+| `CSTOPB` | 2 个停止位;不设置则默认 1 个停止位 |
|
|
|
|
|
+| `CREAD` | 接收使能 |
|
|
|
|
|
+| `PARENB` | 使能奇偶校验 |
|
|
|
|
|
+| `PARODD` | 使用奇校验,而不是偶校验 |
|
|
|
|
|
+| `HUPCL` | 关闭时挂断调制解调器 |
|
|
|
|
|
+| `CLOCAL` | 忽略调制解调器控制线 |
|
|
|
|
|
+| `CRTSCTS` | 使能硬件流控 |
|
|
|
|
|
|
|
|
Linux 下波特率由 `CBAUD` 位掩码的若干 bit 指定,并提供 `cfgetispeed()`/`cfsetispeed()`/`cfsetospeed()`/`cfsetspeed()` 函数获取与设置。
|
|
Linux 下波特率由 `CBAUD` 位掩码的若干 bit 指定,并提供 `cfgetispeed()`/`cfsetispeed()`/`cfsetospeed()`/`cfsetspeed()` 函数获取与设置。
|
|
|
|
|
|
|
@@ -797,37 +813,37 @@ Linux 下波特率由 `CBAUD` 位掩码的若干 bit 指定,并提供 `cfgetis
|
|
|
|
|
|
|
|
控制终端的本地数据处理和工作模式。
|
|
控制终端的本地数据处理和工作模式。
|
|
|
|
|
|
|
|
-| 标志 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `ISIG` | 收到信号字符(INTR、QUIT 等)则产生相应信号 |
|
|
|
|
|
-| `ICANON` | 启用规范模式 |
|
|
|
|
|
-| `ECHO` | 启用输入字符的本地回显功能 |
|
|
|
|
|
-| `ECHOE` | 若设置 `ICANON`,则允许退格操作 |
|
|
|
|
|
-| `ECHOK` | 若设置 `ICANON`,则 KILL 字符会删除当前行 |
|
|
|
|
|
-| `ECHONL` | 若设置 `ICANON`,则允许回显换行符 |
|
|
|
|
|
-| `ECHOCTL` | 若设置 `ECHO`,控制字符会显示成 `^X` |
|
|
|
|
|
-| `ECHOPRT` | 若设置 `ICANON` 和 `IECHO`,删除字符和被删除字符都显示 |
|
|
|
|
|
-| `ECHOKE` | 若设置 `ICANON`,允许回显 `ECHOE`/`ECHOPRT` 中设定的 KILL 字符 |
|
|
|
|
|
-| `NOFLSH` | 通常收到 INTR/QUIT/SUSP 会清空输入输出队列;设置该标志则不清空 |
|
|
|
|
|
-| `TOSTOP` | 后台进程写控制终端时,系统向该进程组发送 `SIGTTOU` |
|
|
|
|
|
-| `IEXTEN` | 启用输入处理功能 |
|
|
|
|
|
|
|
+| 标志 | 含义 |
|
|
|
|
|
+| --------- | -------------------------------------------------------------- |
|
|
|
|
|
+| `ISIG` | 收到信号字符(INTR、QUIT 等)则产生相应信号 |
|
|
|
|
|
+| `ICANON` | 启用规范模式 |
|
|
|
|
|
+| `ECHO` | 启用输入字符的本地回显功能 |
|
|
|
|
|
+| `ECHOE` | 若设置 `ICANON`,则允许退格操作 |
|
|
|
|
|
+| `ECHOK` | 若设置 `ICANON`,则 KILL 字符会删除当前行 |
|
|
|
|
|
+| `ECHONL` | 若设置 `ICANON`,则允许回显换行符 |
|
|
|
|
|
+| `ECHOCTL` | 若设置 `ECHO`,控制字符会显示成 `^X` |
|
|
|
|
|
+| `ECHOPRT` | 若设置 `ICANON` 和 `IECHO`,删除字符和被删除字符都显示 |
|
|
|
|
|
+| `ECHOKE` | 若设置 `ICANON`,允许回显 `ECHOE`/`ECHOPRT` 中设定的 KILL 字符 |
|
|
|
|
|
+| `NOFLSH` | 通常收到 INTR/QUIT/SUSP 会清空输入输出队列;设置该标志则不清空 |
|
|
|
|
|
+| `TOSTOP` | 后台进程写控制终端时,系统向该进程组发送 `SIGTTOU` |
|
|
|
|
|
+| `IEXTEN` | 启用输入处理功能 |
|
|
|
|
|
|
|
|
### 2.2.5 特殊控制字符 c_cc
|
|
### 2.2.5 特殊控制字符 c_cc
|
|
|
|
|
|
|
|
-| 宏 | 对应键 | 作用 |
|
|
|
|
|
-| -- | ------ | ---- |
|
|
|
|
|
-| `VEOF` | Ctrl+D | 文件结尾符 EOF;read 返回 0 表示文件结束 |
|
|
|
|
|
-| `VEOL` | CR | 附加行结尾符 |
|
|
|
|
|
-| `VEOL2` | LF | 第二行结尾符 |
|
|
|
|
|
-| `VERASE` | Backspace | 删除输入行最后一个字符 |
|
|
|
|
|
-| `VINTR` | Ctrl+C | 向与终端相连的进程发送 `SIGINT` |
|
|
|
|
|
-| `VKILL` | Ctrl+U | 删除整个输入行 |
|
|
|
|
|
-| `VMIN` | — | 非规范模式下,最少读取的字符数 MIN |
|
|
|
|
|
-| `VQUIT` | Ctrl+Z | 发送 `SIGQUIT` |
|
|
|
|
|
-| `VSTART` | Ctrl+Q | 重新启动被 STOP 暂停的输出 |
|
|
|
|
|
-| `VSTOP` | Ctrl+S | 停止向终端的进一步输出(XON/XOFF 流控) |
|
|
|
|
|
-| `VSUSP` | Ctrl+Z | 发送 `SIGSUSP`,挂起当前应用程序 |
|
|
|
|
|
-| `VTIME` | — | 非规范模式下,读取每个字符之间的超时(单位:十分之一秒) |
|
|
|
|
|
|
|
+| 宏 | 对应键 | 作用 |
|
|
|
|
|
+| -------- | --------- | -------------------------------------------------------- |
|
|
|
|
|
+| `VEOF` | Ctrl+D | 文件结尾符 EOF;read 返回 0 表示文件结束 |
|
|
|
|
|
+| `VEOL` | CR | 附加行结尾符 |
|
|
|
|
|
+| `VEOL2` | LF | 第二行结尾符 |
|
|
|
|
|
+| `VERASE` | Backspace | 删除输入行最后一个字符 |
|
|
|
|
|
+| `VINTR` | Ctrl+C | 向与终端相连的进程发送 `SIGINT` |
|
|
|
|
|
+| `VKILL` | Ctrl+U | 删除整个输入行 |
|
|
|
|
|
+| `VMIN` | — | 非规范模式下,最少读取的字符数 MIN |
|
|
|
|
|
+| `VQUIT` | Ctrl+Z | 发送 `SIGQUIT` |
|
|
|
|
|
+| `VSTART` | Ctrl+Q | 重新启动被 STOP 暂停的输出 |
|
|
|
|
|
+| `VSTOP` | Ctrl+S | 停止向终端的进一步输出(XON/XOFF 流控) |
|
|
|
|
|
+| `VSUSP` | Ctrl+Z | 发送 `SIGSUSP`,挂起当前应用程序 |
|
|
|
|
|
+| `VTIME` | — | 非规范模式下,读取每个字符之间的超时(单位:十分之一秒) |
|
|
|
|
|
|
|
|
## 2.3 终端的三种工作模式
|
|
## 2.3 终端的三种工作模式
|
|
|
|
|
|
|
@@ -836,12 +852,12 @@ Linux 下波特率由 `CBAUD` 位掩码的若干 bit 指定,并提供 `cfgetis
|
|
|
- **规范模式(canonical)**:所有输入基于行处理。用户输入行结束符(回车、EOF 等)之前,`read()` 读不到任何字符;除 EOF 外的行结束符与普通字符一样被读入缓冲区;支持行编辑,一次 `read()` 最多读一行。
|
|
- **规范模式(canonical)**:所有输入基于行处理。用户输入行结束符(回车、EOF 等)之前,`read()` 读不到任何字符;除 EOF 外的行结束符与普通字符一样被读入缓冲区;支持行编辑,一次 `read()` 最多读一行。
|
|
|
- **非规范模式(non-canonical)**:所有输入即时有效,不需要行结束符,不可行编辑。由 `MIN`(`c_cc[VMIN]`)与 `TIME`(`c_cc[VTIME]`)决定 `read()` 行为:
|
|
- **非规范模式(non-canonical)**:所有输入即时有效,不需要行结束符,不可行编辑。由 `MIN`(`c_cc[VMIN]`)与 `TIME`(`c_cc[VTIME]`)决定 `read()` 行为:
|
|
|
|
|
|
|
|
-| MIN | TIME | read() 行为 |
|
|
|
|
|
-| --- | ---- | ----------- |
|
|
|
|
|
-| 0 | 0 | 总是立即返回:有数据则读并返回字节数,否则返回 0 |
|
|
|
|
|
-| >0 | 0 | 阻塞直到有 MIN 个字符可读才返回;到达文件尾返回 0 |
|
|
|
|
|
-| 0 | >0 | 只要有数据可读,或经过 TIME 个十分之一秒,立即返回;超时无数据返回 0 |
|
|
|
|
|
-| >0 | >0 | 有 MIN 个字节可读,或两字符间隔超过 TIME 个十分之一秒才返回;至少读一个字节 |
|
|
|
|
|
|
|
+| MIN | TIME | read() 行为 |
|
|
|
|
|
+| --- | ---- | --------------------------------------------------------------------------- |
|
|
|
|
|
+| 0 | 0 | 总是立即返回:有数据则读并返回字节数,否则返回 0 |
|
|
|
|
|
+| >0 | 0 | 阻塞直到有 MIN 个字符可读才返回;到达文件尾返回 0 |
|
|
|
|
|
+| 0 | >0 | 只要有数据可读,或经过 TIME 个十分之一秒,立即返回;超时无数据返回 0 |
|
|
|
|
|
+| >0 | >0 | 有 MIN 个字节可读,或两字符间隔超过 TIME 个十分之一秒才返回;至少读一个字节 |
|
|
|
|
|
|
|
|
- **原始模式(raw)**:严格说是一种特殊的非规范模式。所有输入数据以字节为单位处理,终端不回显,禁用输入输出字符的所有特殊处理。通过 `cfmakeraw()` 设置,其内部等价于:
|
|
- **原始模式(raw)**:严格说是一种特殊的非规范模式。所有输入数据以字节为单位处理,终端不回显,禁用输入输出字符的所有特殊处理。通过 `cfmakeraw()` 设置,其内部等价于:
|
|
|
|
|
|
|
@@ -858,42 +874,42 @@ termios_p->c_cflag |= CS8;
|
|
|
|
|
|
|
|
## 2.4 串口编程 API 汇总
|
|
## 2.4 串口编程 API 汇总
|
|
|
|
|
|
|
|
-| 函数 | 原型 | 作用 |
|
|
|
|
|
-| ---- | ---- | ---- |
|
|
|
|
|
-| `tcgetattr` | `int tcgetattr(int fd, struct termios *termios_p)` | 获取当前配置(便于事后恢复) |
|
|
|
|
|
-| `cfmakeraw` | `void cfmakeraw(struct termios *termios_p)` | 配置为原始模式 |
|
|
|
|
|
-| `cfsetispeed` | `int cfsetispeed(struct termios *, speed_t)` | 设置输入波特率 |
|
|
|
|
|
-| `cfsetospeed` | `int cfsetospeed(struct termios *, speed_t)` | 设置输出波特率 |
|
|
|
|
|
-| `cfsetspeed` | `int cfsetspeed(struct termios *, speed_t)` | 一次设置输入输出波特率 |
|
|
|
|
|
-| `tcflush` | `int tcflush(int fd, int queue_selector)` | 清空输入/输出缓冲区 |
|
|
|
|
|
-| `tcdrain` | `int tcdrain(int fd)` | 阻塞直到输出缓冲区数据全部发送完毕 |
|
|
|
|
|
-| `tcflow` | `int tcflow(int fd, int action)` | 暂停/重启数据收发 |
|
|
|
|
|
-| `tcsetattr` | `int tcsetattr(int fd, int optional_actions, const struct termios *)` | 写入配置使其生效 |
|
|
|
|
|
-| `read`/`write` | — | 读写数据 |
|
|
|
|
|
|
|
+| 函数 | 原型 | 作用 |
|
|
|
|
|
+| -------------- | --------------------------------------------------------------------- | ---------------------------------- |
|
|
|
|
|
+| `tcgetattr` | `int tcgetattr(int fd, struct termios *termios_p)` | 获取当前配置(便于事后恢复) |
|
|
|
|
|
+| `cfmakeraw` | `void cfmakeraw(struct termios *termios_p)` | 配置为原始模式 |
|
|
|
|
|
+| `cfsetispeed` | `int cfsetispeed(struct termios *, speed_t)` | 设置输入波特率 |
|
|
|
|
|
+| `cfsetospeed` | `int cfsetospeed(struct termios *, speed_t)` | 设置输出波特率 |
|
|
|
|
|
+| `cfsetspeed` | `int cfsetspeed(struct termios *, speed_t)` | 一次设置输入输出波特率 |
|
|
|
|
|
+| `tcflush` | `int tcflush(int fd, int queue_selector)` | 清空输入/输出缓冲区 |
|
|
|
|
|
+| `tcdrain` | `int tcdrain(int fd)` | 阻塞直到输出缓冲区数据全部发送完毕 |
|
|
|
|
|
+| `tcflow` | `int tcflow(int fd, int action)` | 暂停/重启数据收发 |
|
|
|
|
|
+| `tcsetattr` | `int tcsetattr(int fd, int optional_actions, const struct termios *)` | 写入配置使其生效 |
|
|
|
|
|
+| `read`/`write` | — | 读写数据 |
|
|
|
|
|
|
|
|
`tcflush()` 的 `queue_selector`:
|
|
`tcflush()` 的 `queue_selector`:
|
|
|
|
|
|
|
|
-| 取值 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `TCIFLUSH` | 清空接收到但未被读取的数据 |
|
|
|
|
|
-| `TCOFLUSH` | 清空尚未传输成功的输出数据 |
|
|
|
|
|
-| `TCIOFLUSH` | 前两种都清空 |
|
|
|
|
|
|
|
+| 取值 | 含义 |
|
|
|
|
|
+| ----------- | -------------------------- |
|
|
|
|
|
+| `TCIFLUSH` | 清空接收到但未被读取的数据 |
|
|
|
|
|
+| `TCOFLUSH` | 清空尚未传输成功的输出数据 |
|
|
|
|
|
+| `TCIOFLUSH` | 前两种都清空 |
|
|
|
|
|
|
|
|
`tcflow()` 的 `action`:
|
|
`tcflow()` 的 `action`:
|
|
|
|
|
|
|
|
-| 取值 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `TCOOFF` | 暂停数据输出 |
|
|
|
|
|
-| `TCOON` | 重新启动暂停的输出 |
|
|
|
|
|
-| `TCIOFF` | 发送 STOP 字符,停止终端设备向系统发送数据 |
|
|
|
|
|
-| `TCION` | 发送 START 字符,启动终端设备向系统发送数据 |
|
|
|
|
|
|
|
+| 取值 | 含义 |
|
|
|
|
|
+| -------- | ------------------------------------------- |
|
|
|
|
|
+| `TCOOFF` | 暂停数据输出 |
|
|
|
|
|
+| `TCOON` | 重新启动暂停的输出 |
|
|
|
|
|
+| `TCIOFF` | 发送 STOP 字符,停止终端设备向系统发送数据 |
|
|
|
|
|
+| `TCION` | 发送 START 字符,启动终端设备向系统发送数据 |
|
|
|
|
|
|
|
|
`tcsetattr()` 的 `optional_actions`(生效时机):
|
|
`tcsetattr()` 的 `optional_actions`(生效时机):
|
|
|
|
|
|
|
|
-| 取值 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `TCSANOW` | 配置立即生效 |
|
|
|
|
|
-| `TCSADRAIN` | 在所有写入 fd 的输出传输完毕之后生效 |
|
|
|
|
|
|
|
+| 取值 | 含义 |
|
|
|
|
|
+| ----------- | ------------------------------------------ |
|
|
|
|
|
+| `TCSANOW` | 配置立即生效 |
|
|
|
|
|
+| `TCSADRAIN` | 在所有写入 fd 的输出传输完毕之后生效 |
|
|
|
| `TCSAFLUSH` | 所有已接收但未读取的输入在配置生效前被丢弃 |
|
|
| `TCSAFLUSH` | 所有已接收但未读取的输入在配置生效前被丢弃 |
|
|
|
|
|
|
|
|
**配置步骤**(以原始模式为例):
|
|
**配置步骤**(以原始模式为例):
|
|
@@ -1343,35 +1359,35 @@ flowchart TB
|
|
|
|
|
|
|
|
`/dev/snd` 下常见设备节点:
|
|
`/dev/snd` 下常见设备节点:
|
|
|
|
|
|
|
|
-| 设备节点 | 含义 |
|
|
|
|
|
-| -------- | ---- |
|
|
|
|
|
|
|
+| 设备节点 | 含义 |
|
|
|
|
|
+| ----------- | ----------------------------------------------------------- |
|
|
|
| `controlC0` | 声卡控制设备(通道选择、混音器、麦克风控制等),C0 = 声卡 0 |
|
|
| `controlC0` | 声卡控制设备(通道选择、混音器、麦克风控制等),C0 = 声卡 0 |
|
|
|
-| `pcmC0D0c` | 声卡 0、设备 0 的**录音** PCM 设备(c = capture) |
|
|
|
|
|
-| `pcmC0D0p` | 声卡 0、设备 0 的**播放** PCM 设备(p = playback) |
|
|
|
|
|
-| `pcmC0D1c` | 声卡 0、设备 1 的录音 PCM 设备 |
|
|
|
|
|
-| `pcmC0D1p` | 声卡 0、设备 1 的播放 PCM 设备 |
|
|
|
|
|
-| `timer` | 定时器 |
|
|
|
|
|
|
|
+| `pcmC0D0c` | 声卡 0、设备 0 的**录音** PCM 设备(c = capture) |
|
|
|
|
|
+| `pcmC0D0p` | 声卡 0、设备 0 的**播放** PCM 设备(p = playback) |
|
|
|
|
|
+| `pcmC0D1c` | 声卡 0、设备 1 的录音 PCM 设备 |
|
|
|
|
|
+| `pcmC0D1p` | 声卡 0、设备 1 的播放 PCM 设备 |
|
|
|
|
|
+| `timer` | 定时器 |
|
|
|
|
|
|
|
|
`/proc/asound` 目录记录声卡信息:
|
|
`/proc/asound` 目录记录声卡信息:
|
|
|
|
|
|
|
|
-| 文件/命令 | 作用 |
|
|
|
|
|
-| --------- | ---- |
|
|
|
|
|
-| `cat /proc/asound/cards` | 列出系统中注册的所有声卡 |
|
|
|
|
|
|
|
+| 文件/命令 | 作用 |
|
|
|
|
|
+| -------------------------- | ----------------------------------------------------- |
|
|
|
|
|
+| `cat /proc/asound/cards` | 列出系统中注册的所有声卡 |
|
|
|
| `cat /proc/asound/devices` | 列出所有声卡注册的设备(control、pcm、timer、seq 等) |
|
|
| `cat /proc/asound/devices` | 列出所有声卡注册的设备(control、pcm、timer、seq 等) |
|
|
|
-| `cat /proc/asound/pcm` | 列出所有 PCM 设备(playback 与 capture) |
|
|
|
|
|
|
|
+| `cat /proc/asound/pcm` | 列出所有 PCM 设备(playback 与 capture) |
|
|
|
|
|
|
|
|
## 3.3 音频基本概念
|
|
## 3.3 音频基本概念
|
|
|
|
|
|
|
|
-| 概念 | 说明 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| 样本长度(Sample) | 记录音频数据最基本的单元;即采样位数/位深度,如 8bit、16bit、24bit |
|
|
|
|
|
-| 声道数(channel) | 单声道 Mono=1,双声道/立体声 Stereo=2 |
|
|
|
|
|
-| 帧(frame) | 一个声音单元,长度 = 样本长度 × 声道数。16bit 双声道一帧 = 16×2/8 = 4 字节 |
|
|
|
|
|
-| 采样率(Sample rate) | 每秒采样次数(针对帧)。8KHz 电话、22.05KHz FM、44.1KHz CD、48KHz 数字电视 |
|
|
|
|
|
-| 交错模式(interleaved) | 数据以连续帧存放:先帧 1 左、右声道样本,再帧 2 左、右……多数情况使用交错模式 |
|
|
|
|
|
-| 周期(period) | 音频设备读写数据的单位,一个周期包含若干帧,如 1024 帧 |
|
|
|
|
|
-| 缓冲区(buffer) | 由若干周期组成的一块空间,如 16 个周期 |
|
|
|
|
|
-| XRUN | 录音时应用读得慢导致数据被覆盖叫 **overrun**;播放时应用写得慢导致缓冲"饿死"叫 **underrun**,统称 XRUN |
|
|
|
|
|
|
|
+| 概念 | 说明 |
|
|
|
|
|
+| ----------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
|
|
|
+| 样本长度(Sample) | 记录音频数据最基本的单元;即采样位数/位深度,如 8bit、16bit、24bit |
|
|
|
|
|
+| 声道数(channel) | 单声道 Mono=1,双声道/立体声 Stereo=2 |
|
|
|
|
|
+| 帧(frame) | 一个声音单元,长度 = 样本长度 × 声道数。16bit 双声道一帧 = 16×2/8 = 4 字节 |
|
|
|
|
|
+| 采样率(Sample rate) | 每秒采样次数(针对帧)。8KHz 电话、22.05KHz FM、44.1KHz CD、48KHz 数字电视 |
|
|
|
|
|
+| 交错模式(interleaved) | 数据以连续帧存放:先帧 1 左、右声道样本,再帧 2 左、右……多数情况使用交错模式 |
|
|
|
|
|
+| 周期(period) | 音频设备读写数据的单位,一个周期包含若干帧,如 1024 帧 |
|
|
|
|
|
+| 缓冲区(buffer) | 由若干周期组成的一块空间,如 16 个周期 |
|
|
|
|
|
+| XRUN | 录音时应用读得慢导致数据被覆盖叫 **overrun**;播放时应用写得慢导致缓冲"饿死"叫 **underrun**,统称 XRUN |
|
|
|
|
|
|
|
|
**为什么要把 buffer 拆成多个 period?** 底层驱动用 DMA 搬运数据,每搬完一个 period 触发一次中断。若一次性搬完整个 buffer,数据量越大延迟越高。周期越小延迟越低,但中断越频繁、CPU 效率越低。因此在延迟可接受的前提下,周期尽量大一些,具体依应用场合而定。
|
|
**为什么要把 buffer 拆成多个 period?** 底层驱动用 DMA 搬运数据,每搬完一个 period 触发一次中断。若一次性搬完整个 buffer,数据量越大延迟越高。周期越小延迟越低,但中断越频繁、CPU 效率越低。因此在延迟可接受的前提下,周期尽量大一些,具体依应用场合而定。
|
|
|
|
|
|
|
@@ -1407,12 +1423,12 @@ int snd_pcm_open(snd_pcm_t **pcmp, const char *name,
|
|
|
int snd_pcm_close(snd_pcm_t *pcm);
|
|
int snd_pcm_close(snd_pcm_t *pcm);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-| 参数 | 说明 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `pcmp` | 返回 PCM 设备句柄(`snd_pcm_t *`) |
|
|
|
|
|
-| `name` | 逻辑设备名,非设备文件名。`"hw:0,0"` = 声卡 0 的 PCM 设备 0,播放对应 `/dev/snd/pcmC0D0p`,录音对应 `pcmC0D0c`;还有 `"plughw:i,j"`、`"default"` 等 |
|
|
|
|
|
-| `stream` | `SND_PCM_STREAM_PLAYBACK`(播放)或 `SND_PCM_STREAM_CAPTURE`(采集) |
|
|
|
|
|
-| `mode` | 通常为 0(默认,阻塞方式);`SND_PCM_NONBLOCK` 为非阻塞 |
|
|
|
|
|
|
|
+| 参数 | 说明 |
|
|
|
|
|
+| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
+| `pcmp` | 返回 PCM 设备句柄(`snd_pcm_t *`) |
|
|
|
|
|
+| `name` | 逻辑设备名,非设备文件名。`"hw:0,0"` = 声卡 0 的 PCM 设备 0,播放对应 `/dev/snd/pcmC0D0p`,录音对应 `pcmC0D0c`;还有 `"plughw:i,j"`、`"default"` 等 |
|
|
|
|
|
+| `stream` | `SND_PCM_STREAM_PLAYBACK`(播放)或 `SND_PCM_STREAM_CAPTURE`(采集) |
|
|
|
|
|
+| `mode` | 通常为 0(默认,阻塞方式);`SND_PCM_NONBLOCK` 为非阻塞 |
|
|
|
|
|
|
|
|
```c
|
|
```c
|
|
|
ret = snd_pcm_open(&pcm_handle, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
|
|
ret = snd_pcm_open(&pcm_handle, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
|
|
@@ -1433,15 +1449,15 @@ snd_pcm_hw_params(pcm_handle, hwparams); /* 加载生效 */
|
|
|
snd_pcm_hw_params_free(hwparams);
|
|
snd_pcm_hw_params_free(hwparams);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-| 设置函数 | 作用 | 关键说明 |
|
|
|
|
|
-| -------- | ---- | -------- |
|
|
|
|
|
-| `snd_pcm_hw_params_set_access()` | 访问类型 | `SND_PCM_ACCESS_RW_INTERLEAVED`(交错,配 `readi/writei`)、`..._RW_NONINTERLEAVED`(配 `readn/writen`) |
|
|
|
|
|
-| `snd_pcm_hw_params_set_format()` | 数据格式 | 常用 `SND_PCM_FORMAT_S16_LE`(有符号 16 位小端) |
|
|
|
|
|
-| `snd_pcm_hw_params_set_channels()` | 声道数 | 2 = 双声道 |
|
|
|
|
|
-| `snd_pcm_hw_params_set_rate()` | 采样率 | `val` 如 44100;`dir`:-1 实际≤val、0 实际=val、1 实际≥val |
|
|
|
|
|
-| `snd_pcm_hw_params_set_period_size()` | 周期大小(帧) | 如 1024 |
|
|
|
|
|
-| `snd_pcm_hw_params_set_buffer_size()` | buffer 大小(帧) | 如 16×1024 |
|
|
|
|
|
-| `snd_pcm_hw_params_set_periods()` | buffer 大小(周期数) | 如 16 |
|
|
|
|
|
|
|
+| 设置函数 | 作用 | 关键说明 |
|
|
|
|
|
+| ------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
+| `snd_pcm_hw_params_set_access()` | 访问类型 | `SND_PCM_ACCESS_RW_INTERLEAVED`(交错,配 `readi/writei`)、`..._RW_NONINTERLEAVED`(配 `readn/writen`) |
|
|
|
|
|
+| `snd_pcm_hw_params_set_format()` | 数据格式 | 常用 `SND_PCM_FORMAT_S16_LE`(有符号 16 位小端) |
|
|
|
|
|
+| `snd_pcm_hw_params_set_channels()` | 声道数 | 2 = 双声道 |
|
|
|
|
|
+| `snd_pcm_hw_params_set_rate()` | 采样率 | `val` 如 44100;`dir`:-1 实际≤val、0 实际=val、1 实际≥val |
|
|
|
|
|
+| `snd_pcm_hw_params_set_period_size()` | 周期大小(帧) | 如 1024 |
|
|
|
|
|
+| `snd_pcm_hw_params_set_buffer_size()` | buffer 大小(帧) | 如 16×1024 |
|
|
|
|
|
+| `snd_pcm_hw_params_set_periods()` | buffer 大小(周期数) | 如 16 |
|
|
|
|
|
|
|
|
> `snd_pcm_hw_params_set_period_size()` 的 `val` 单位是**帧**不是字节;`snd_pcm_hw_params_set_periods()` 的 `val` 单位是**周期**。`snd_pcm_hw_params()` 内部会自动调用 `snd_pcm_prepare()`,使设备进入 `SND_PCM_STATE_PREPARED`。
|
|
> `snd_pcm_hw_params_set_period_size()` 的 `val` 单位是**帧**不是字节;`snd_pcm_hw_params_set_periods()` 的 `val` 单位是**周期**。`snd_pcm_hw_params()` 内部会自动调用 `snd_pcm_prepare()`,使设备进入 `SND_PCM_STATE_PREPARED`。
|
|
|
|
|
|
|
@@ -1464,17 +1480,17 @@ snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t
|
|
|
|
|
|
|
|
`snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm)` 获取当前状态:
|
|
`snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm)` 获取当前状态:
|
|
|
|
|
|
|
|
-| 状态 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `SND_PCM_STATE_OPEN` | 设备已打开(`snd_pcm_open()` 后) |
|
|
|
|
|
-| `SND_PCM_STATE_SETUP` | 设备已初始化、参数已配置好 |
|
|
|
|
|
-| `SND_PCM_STATE_PREPARED` | 设备已就绪,可开始播放/录音 |
|
|
|
|
|
-| `SND_PCM_STATE_RUNNING` | 正在运行(播放/录音中) |
|
|
|
|
|
-| `SND_PCM_STATE_XRUN` | 发生 XRUN;可调 `snd_pcm_prepare()` 恢复 |
|
|
|
|
|
-| `SND_PCM_STATE_DRAINING` | Draining(播放中/采集停止) |
|
|
|
|
|
-| `SND_PCM_STATE_PAUSED` | 暂停 |
|
|
|
|
|
-| `SND_PCM_STATE_SUSPENDED` | 硬件挂起;可用 `snd_pcm_resume()` 精细恢复 |
|
|
|
|
|
-| `SND_PCM_STATE_DISCONNECTED` | 硬件已断开 |
|
|
|
|
|
|
|
+| 状态 | 含义 |
|
|
|
|
|
+| ---------------------------- | ------------------------------------------ |
|
|
|
|
|
+| `SND_PCM_STATE_OPEN` | 设备已打开(`snd_pcm_open()` 后) |
|
|
|
|
|
+| `SND_PCM_STATE_SETUP` | 设备已初始化、参数已配置好 |
|
|
|
|
|
+| `SND_PCM_STATE_PREPARED` | 设备已就绪,可开始播放/录音 |
|
|
|
|
|
+| `SND_PCM_STATE_RUNNING` | 正在运行(播放/录音中) |
|
|
|
|
|
+| `SND_PCM_STATE_XRUN` | 发生 XRUN;可调 `snd_pcm_prepare()` 恢复 |
|
|
|
|
|
+| `SND_PCM_STATE_DRAINING` | Draining(播放中/采集停止) |
|
|
|
|
|
+| `SND_PCM_STATE_PAUSED` | 暂停 |
|
|
|
|
|
+| `SND_PCM_STATE_SUSPENDED` | 硬件挂起;可用 `snd_pcm_resume()` 精细恢复 |
|
|
|
|
|
+| `SND_PCM_STATE_DISCONNECTED` | 硬件已断开 |
|
|
|
|
|
|
|
|
相关函数:
|
|
相关函数:
|
|
|
|
|
|
|
@@ -1495,10 +1511,10 @@ int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params);
|
|
|
|
|
|
|
|
`snd_pcm_readi/writei` 返回负错误码时用 `snd_strerror()` 获取描述:
|
|
`snd_pcm_readi/writei` 返回负错误码时用 `snd_strerror()` 获取描述:
|
|
|
|
|
|
|
|
-| 返回值 | 含义 | 处理建议 |
|
|
|
|
|
-| ------ | ---- | -------- |
|
|
|
|
|
-| `-EBADFD` | PCM 设备状态不对 | 需处于 PREPARED 或 RUNNING,检查状态转换 |
|
|
|
|
|
-| `-EPIPE` | 发生 XRUN | `snd_pcm_drop()` 停止,或 `snd_pcm_prepare()` 恢复 |
|
|
|
|
|
|
|
+| 返回值 | 含义 | 处理建议 |
|
|
|
|
|
+| ----------- | --------------------- | --------------------------------------------------------- |
|
|
|
|
|
+| `-EBADFD` | PCM 设备状态不对 | 需处于 PREPARED 或 RUNNING,检查状态转换 |
|
|
|
|
|
+| `-EPIPE` | 发生 XRUN | `snd_pcm_drop()` 停止,或 `snd_pcm_prepare()` 恢复 |
|
|
|
| `-ESTRPIPE` | 硬件挂起(SUSPENDED) | `snd_pcm_resume()` 精细恢复;不支持则 `snd_pcm_prepare()` |
|
|
| `-ESTRPIPE` | 硬件挂起(SUSPENDED) | `snd_pcm_resume()` 精细恢复;不支持则 `snd_pcm_prepare()` |
|
|
|
|
|
|
|
|
## 3.8 混音器 API
|
|
## 3.8 混音器 API
|
|
@@ -1516,20 +1532,20 @@ snd_mixer_load(mixer); /* 加载 */
|
|
|
|
|
|
|
|
遍历与取值:
|
|
遍历与取值:
|
|
|
|
|
|
|
|
-| 函数 | 作用 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `snd_mixer_first_elem(mixer)` / `snd_mixer_last_elem(mixer)` | 第一个/最后一个元素 |
|
|
|
|
|
-| `snd_mixer_elem_next(elem)` / `snd_mixer_elem_prev(elem)` | 下一个/上一个元素 |
|
|
|
|
|
-| `snd_mixer_selem_get_name(elem)` | 获取元素名字(如 `"Headphone"`、`"Playback"`) |
|
|
|
|
|
-| `snd_mixer_selem_has_playback_volume(elem)` / `..._has_capture_volume(elem)` | 是否 volume 类型 |
|
|
|
|
|
-| `snd_mixer_selem_has_playback_switch(elem)` / `..._has_capture_switch(elem)` | 是否 switch(bool)类型 |
|
|
|
|
|
-| `snd_mixer_selem_has_playback_channel(elem, ch)` / `..._has_capture_channel(elem, ch)` | 是否含指定通道 |
|
|
|
|
|
-| `snd_mixer_selem_is_playback_mono(elem)` / `..._is_capture_mono(elem)` | 是否单声道 |
|
|
|
|
|
-| `snd_mixer_selem_get_playback_volume_range(elem, &min, &max)` | 获取音量范围 |
|
|
|
|
|
-| `snd_mixer_selem_get_playback_volume(elem, ch, &value)` | 获取某声道音量 |
|
|
|
|
|
-| `snd_mixer_selem_set_playback_volume(elem, ch, value)` | 设置某声道音量 |
|
|
|
|
|
-| `snd_mixer_selem_set_playback_volume_all(elem, value)` | 设置所有声道音量 |
|
|
|
|
|
-| `snd_mixer_close(mixer)` | 关闭混音器 |
|
|
|
|
|
|
|
+| 函数 | 作用 |
|
|
|
|
|
+| -------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
|
|
|
|
+| `snd_mixer_first_elem(mixer)` / `snd_mixer_last_elem(mixer)` | 第一个/最后一个元素 |
|
|
|
|
|
+| `snd_mixer_elem_next(elem)` / `snd_mixer_elem_prev(elem)` | 下一个/上一个元素 |
|
|
|
|
|
+| `snd_mixer_selem_get_name(elem)` | 获取元素名字(如 `"Headphone"`、`"Playback"`) |
|
|
|
|
|
+| `snd_mixer_selem_has_playback_volume(elem)` / `..._has_capture_volume(elem)` | 是否 volume 类型 |
|
|
|
|
|
+| `snd_mixer_selem_has_playback_switch(elem)` / `..._has_capture_switch(elem)` | 是否 switch(bool)类型 |
|
|
|
|
|
+| `snd_mixer_selem_has_playback_channel(elem, ch)` / `..._has_capture_channel(elem, ch)` | 是否含指定通道 |
|
|
|
|
|
+| `snd_mixer_selem_is_playback_mono(elem)` / `..._is_capture_mono(elem)` | 是否单声道 |
|
|
|
|
|
+| `snd_mixer_selem_get_playback_volume_range(elem, &min, &max)` | 获取音量范围 |
|
|
|
|
|
+| `snd_mixer_selem_get_playback_volume(elem, ch, &value)` | 获取某声道音量 |
|
|
|
|
|
+| `snd_mixer_selem_set_playback_volume(elem, ch, value)` | 设置某声道音量 |
|
|
|
|
|
+| `snd_mixer_selem_set_playback_volume_all(elem, value)` | 设置所有声道音量 |
|
|
|
|
|
+| `snd_mixer_close(mixer)` | 关闭混音器 |
|
|
|
|
|
|
|
|
声道枚举 `snd_mixer_selem_channel_id_t`:`SND_MIXER_SCHN_FRONT_LEFT`(0,左前)、`SND_MIXER_SCHN_FRONT_RIGHT`(右前)、`SND_MIXER_SCHN_REAR_LEFT`、`SND_MIXER_SCHN_REAR_RIGHT`、`SND_MIXER_SCHN_FRONT_CENTER`、`SND_MIXER_SCHN_WOOFER`、`SND_MIXER_SCHN_MONO`(= 左前)。
|
|
声道枚举 `snd_mixer_selem_channel_id_t`:`SND_MIXER_SCHN_FRONT_LEFT`(0,左前)、`SND_MIXER_SCHN_FRONT_RIGHT`(右前)、`SND_MIXER_SCHN_REAR_LEFT`、`SND_MIXER_SCHN_REAR_RIGHT`、`SND_MIXER_SCHN_FRONT_CENTER`、`SND_MIXER_SCHN_WOOFER`、`SND_MIXER_SCHN_MONO`(= 左前)。
|
|
|
|
|
|
|
@@ -1537,16 +1553,16 @@ snd_mixer_load(mixer); /* 加载 */
|
|
|
|
|
|
|
|
出厂系统已移植 alsa-lib 与 alsa-utils,可直接使用:
|
|
出厂系统已移植 alsa-lib 与 alsa-utils,可直接使用:
|
|
|
|
|
|
|
|
-| 工具 | 用途 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `aplay xxx.wav` | 播放 wav(不支持 mp3 解码) |
|
|
|
|
|
-| `arecord -f cd -d 10 test.wav` | 录音 10 秒,`-f cd` = 16bit little endian 44100 stereo |
|
|
|
|
|
-| `alsamixer` | 字符图形化混音器配置界面(F3 Playback / F4 Capture / F5 All,F6 选声卡) |
|
|
|
|
|
-| `amixer scontrols` / `amixer scontents` | 列出配置项 / 查看配置说明 |
|
|
|
|
|
-| `amixer sset Headphone 100,100` | 设置耳机音量左右声道 |
|
|
|
|
|
-| `amixer sset "Headphone Playback ZC" off` | 开关某项 |
|
|
|
|
|
-| `alsactl -f /var/lib/alsa/asound.state store` | 保存声卡配置 |
|
|
|
|
|
-| `alsactl -f /var/lib/alsa/asound.state restore` | 加载声卡配置 |
|
|
|
|
|
|
|
+| 工具 | 用途 |
|
|
|
|
|
+| ----------------------------------------------- | ------------------------------------------------------------------------ |
|
|
|
|
|
+| `aplay xxx.wav` | 播放 wav(不支持 mp3 解码) |
|
|
|
|
|
+| `arecord -f cd -d 10 test.wav` | 录音 10 秒,`-f cd` = 16bit little endian 44100 stereo |
|
|
|
|
|
+| `alsamixer` | 字符图形化混音器配置界面(F3 Playback / F4 Capture / F5 All,F6 选声卡) |
|
|
|
|
|
+| `amixer scontrols` / `amixer scontents` | 列出配置项 / 查看配置说明 |
|
|
|
|
|
+| `amixer sset Headphone 100,100` | 设置耳机音量左右声道 |
|
|
|
|
|
+| `amixer sset "Headphone Playback ZC" off` | 开关某项 |
|
|
|
|
|
+| `alsactl -f /var/lib/alsa/asound.state store` | 保存声卡配置 |
|
|
|
|
|
+| `alsactl -f /var/lib/alsa/asound.state restore` | 加载声卡配置 |
|
|
|
|
|
|
|
|
> 开机自动从 `/var/lib/alsa/asound.state` 加载配置,关机时保存。此文件即 WM8960 声卡配置文件,由 `alsactl` 完成。
|
|
> 开机自动从 `/var/lib/alsa/asound.state` 加载配置,关机时保存。此文件即 WM8960 声卡配置文件,由 `alsactl` 完成。
|
|
|
|
|
|
|
@@ -1578,17 +1594,17 @@ typedef struct WAV_DATA {
|
|
|
} __attribute__ ((packed)) DATA_t;
|
|
} __attribute__ ((packed)) DATA_t;
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-| 字段 | 含义 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `ChunkID` / `Format` | 固定 `"RIFF"` / `"WAVE"` |
|
|
|
|
|
-| `Subchunk1ID` | 固定 `"fmt "` |
|
|
|
|
|
-| `AudioFormat` | `1` 表示 PCM |
|
|
|
|
|
-| `NumChannels` | 声道数 |
|
|
|
|
|
-| `SampleRate` | 采样率 |
|
|
|
|
|
-| `ByteRate` | 每秒字节数 = 采样率 × 声道数 × 位深/8 |
|
|
|
|
|
-| `BlockAlign` | 一帧字节数 = 声道数 × 位深/8(即 frame_bytes) |
|
|
|
|
|
-| `BitsPerSample` | 位深 |
|
|
|
|
|
-| `Subchunk2ID` / `Subchunk2Size` | 数据块标识 `"data"` 与大小 |
|
|
|
|
|
|
|
+| 字段 | 含义 |
|
|
|
|
|
+| ------------------------------- | ---------------------------------------------- |
|
|
|
|
|
+| `ChunkID` / `Format` | 固定 `"RIFF"` / `"WAVE"` |
|
|
|
|
|
+| `Subchunk1ID` | 固定 `"fmt "` |
|
|
|
|
|
+| `AudioFormat` | `1` 表示 PCM |
|
|
|
|
|
+| `NumChannels` | 声道数 |
|
|
|
|
|
+| `SampleRate` | 采样率 |
|
|
|
|
|
+| `ByteRate` | 每秒字节数 = 采样率 × 声道数 × 位深/8 |
|
|
|
|
|
+| `BlockAlign` | 一帧字节数 = 声道数 × 位深/8(即 frame_bytes) |
|
|
|
|
|
+| `BitsPerSample` | 位深 |
|
|
|
|
|
+| `Subchunk2ID` / `Subchunk2Size` | 数据块标识 `"data"` 与大小 |
|
|
|
|
|
|
|
|
**解析思路**:读 RIFF 头并校验 `"RIFF"`/`"WAVE"`;读 `fmt ` 子块并校验 `"fmt "`,同时拿到 `SampleRate`、`NumChannels`、`BlockAlign`(用于配置 ALSA 参数与缓冲区);用 `lseek` 跳过 `sizeof(RIFF_t)+8+Subchunk1Size`,循环读 `DATA_t` 找 `"data"` 块(不是则 `lseek` 跳过该块),找到后文件位置即 PCM 数据起点。
|
|
**解析思路**:读 RIFF 头并校验 `"RIFF"`/`"WAVE"`;读 `fmt ` 子块并校验 `"fmt "`,同时拿到 `SampleRate`、`NumChannels`、`BlockAlign`(用于配置 ALSA 参数与缓冲区);用 `lseek` 跳过 `sizeof(RIFF_t)+8+Subchunk1Size`,循环读 `DATA_t` 找 `"data"` 块(不是则 `lseek` 跳过该块),找到后文件位置即 PCM 数据起点。
|
|
|
|
|
|
|
@@ -1667,7 +1683,7 @@ static int snd_pcm_init(void)
|
|
|
goto err2;
|
|
goto err2;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**************
|
|
|
|
|
|
|
+ /**************
|
|
|
设置参数
|
|
设置参数
|
|
|
***************/
|
|
***************/
|
|
|
/* 设置访问类型: 交错模式 */
|
|
/* 设置访问类型: 交错模式 */
|
|
@@ -1923,7 +1939,7 @@ static int snd_pcm_init(void)
|
|
|
goto err2;
|
|
goto err2;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**************
|
|
|
|
|
|
|
+ /**************
|
|
|
设置参数
|
|
设置参数
|
|
|
***************/
|
|
***************/
|
|
|
/* 设置访问类型: 交错模式 */
|
|
/* 设置访问类型: 交错模式 */
|
|
@@ -2223,12 +2239,12 @@ ${CC} -o pcm_playback_mixer pcm_playback_mixer.c -lasound
|
|
|
|
|
|
|
|
`user 空间的 alsa-lib` 使用**逻辑设备名**而非设备节点。`snd_pcm_open()` 会加载 `/usr/share/alsa/alsa.conf` 并解析,其中又会加载 `/etc/asound.conf` 与 `~/.asoundrc`。每个 `pcm.name { ... }` 定义了一个插件,`type` 字段指定插件类型:
|
|
`user 空间的 alsa-lib` 使用**逻辑设备名**而非设备节点。`snd_pcm_open()` 会加载 `/usr/share/alsa/alsa.conf` 并解析,其中又会加载 `/etc/asound.conf` 与 `~/.asoundrc`。每个 `pcm.name { ... }` 定义了一个插件,`type` 字段指定插件类型:
|
|
|
|
|
|
|
|
-| 插件 | 作用 |
|
|
|
|
|
-| ---- | ---- |
|
|
|
|
|
-| `hw` | 直接与 ALSA 内核驱动通信,无任何转换的原始通信 |
|
|
|
|
|
-| `plughw` | 提供采样率转换等软件特性(硬件不支持时可用) |
|
|
|
|
|
-| `dmix` | 混音,把多个应用程序的音频数据混合 |
|
|
|
|
|
-| `softvol` | 软件音量 |
|
|
|
|
|
|
|
+| 插件 | 作用 |
|
|
|
|
|
+| --------- | ---------------------------------------------- |
|
|
|
|
|
+| `hw` | 直接与 ALSA 内核驱动通信,无任何转换的原始通信 |
|
|
|
|
|
+| `plughw` | 提供采样率转换等软件特性(硬件不支持时可用) |
|
|
|
|
|
+| `dmix` | 混音,把多个应用程序的音频数据混合 |
|
|
|
|
|
+| `softvol` | 软件音量 |
|
|
|
|
|
|
|
|
`"hw:i,j"` 就是名为 `hw` 的插件,`i` 是声卡号、`j` 是设备号。
|
|
`"hw:i,j"` 就是名为 `hw` 的插件,`i` 是声卡号、`j` 是设备号。
|
|
|
|
|
|
|
@@ -2236,13 +2252,13 @@ ${CC} -o pcm_playback_mixer pcm_playback_mixer.c -lasound
|
|
|
|
|
|
|
|
# 跨平台对比
|
|
# 跨平台对比
|
|
|
|
|
|
|
|
-| 主题 | I.MX6ULL(本教程) | STM32(裸机/HAL) | RK3568(Linux) |
|
|
|
|
|
-| ---- | ------------------ | ----------------- | --------------- |
|
|
|
|
|
-| 摄像头 | V4L2 `/dev/videoX`,ov5640/ov2640/ov7725/UVC,ioctl + mmap 队列 | DCMI + DMA,直接操作寄存器/LL 库 | V4L2 + Media Controller/V4L2 subdev,Rockchip ISP |
|
|
|
|
|
-| 串口 | termios API,`/dev/ttymxcX` | USART,HAL_UART_Transmit/Receive + 中断/DMA | termios API,`/dev/ttySX` |
|
|
|
|
|
-| 音频 | ALSA + alsa-lib,WM8960/ES8388 | I2S + 外部 codec,寄存器配置 | ALSA + alsa-lib,通常走 GStreamer/ASoC |
|
|
|
|
|
-| 编程层 | 用户态,统一设备框架 | 裸机/RTOS,直接寄存器 | 用户态,统一设备框架 |
|
|
|
|
|
-| 关键差异 | 需掌握框架接口与设备节点 | 需手工配置时钟/引脚/DMA | 同 V4L2/ALSA,但多平面、复杂管线常见 |
|
|
|
|
|
|
|
+| 主题 | I.MX6ULL(本教程) | STM32(裸机/HAL) | RK3568(Linux) |
|
|
|
|
|
+| -------- | --------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------- |
|
|
|
|
|
+| 摄像头 | V4L2 `/dev/videoX`,ov5640/ov2640/ov7725/UVC,ioctl + mmap 队列 | DCMI + DMA,直接操作寄存器/LL 库 | V4L2 + Media Controller/V4L2 subdev,Rockchip ISP |
|
|
|
|
|
+| 串口 | termios API,`/dev/ttymxcX` | USART,HAL_UART_Transmit/Receive + 中断/DMA | termios API,`/dev/ttySX` |
|
|
|
|
|
+| 音频 | ALSA + alsa-lib,WM8960/ES8388 | I2S + 外部 codec,寄存器配置 | ALSA + alsa-lib,通常走 GStreamer/ASoC |
|
|
|
|
|
+| 编程层 | 用户态,统一设备框架 | 裸机/RTOS,直接寄存器 | 用户态,统一设备框架 |
|
|
|
|
|
+| 关键差异 | 需掌握框架接口与设备节点 | 需手工配置时钟/引脚/DMA | 同 V4L2/ALSA,但多平面、复杂管线常见 |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -2255,6 +2271,7 @@ ${CC} -o pcm_playback_mixer pcm_playback_mixer.c -lasound
|
|
|
**详细解答**:内核维护一个帧缓冲队列。应用 `VIDIOC_QBUF` 把空闲缓冲交给驱动,驱动填充一帧后缓冲变为"满";应用 `VIDIOC_DQBUF` 取走满缓冲(出队),处理完再 `VIDIOC_QBUF` 归还(入队)。这样避免了每帧内存拷贝——`mmap` 后应用直接读内核缓冲,即零拷贝。`count` 一般取 3~4,太多浪费内存、太少可能丢帧。
|
|
**详细解答**:内核维护一个帧缓冲队列。应用 `VIDIOC_QBUF` 把空闲缓冲交给驱动,驱动填充一帧后缓冲变为"满";应用 `VIDIOC_DQBUF` 取走满缓冲(出队),处理完再 `VIDIOC_QBUF` 归还(入队)。这样避免了每帧内存拷贝——`mmap` 后应用直接读内核缓冲,即零拷贝。`count` 一般取 3~4,太多浪费内存、太少可能丢帧。
|
|
|
|
|
|
|
|
**追问**
|
|
**追问**
|
|
|
|
|
+
|
|
|
1. `VIDIOC_DQBUF` 在缓冲为空时会怎样?(阻塞直到驱动填入一帧;非阻塞打开则立即返回 `EAGAIN`)
|
|
1. `VIDIOC_DQBUF` 在缓冲为空时会怎样?(阻塞直到驱动填入一帧;非阻塞打开则立即返回 `EAGAIN`)
|
|
|
2. `V4L2_MEMORY_MMAP` 与 `V4L2_MEMORY_USERPTR` 区别?(MMAP 由内核分配、应用映射;USERPTR 由应用提供缓冲指针)
|
|
2. `V4L2_MEMORY_MMAP` 与 `V4L2_MEMORY_USERPTR` 区别?(MMAP 由内核分配、应用映射;USERPTR 由应用提供缓冲指针)
|
|
|
|
|
|
|
@@ -2275,6 +2292,7 @@ if (V4L2_PIX_FMT_RGB565 != fmt.fmt.pix.pixelformat) { /* 不支持,退化为 Y
|
|
|
注意 `VIDIOC_TRY_FMT` 只试不改,可在正式设置前探测。
|
|
注意 `VIDIOC_TRY_FMT` 只试不改,可在正式设置前探测。
|
|
|
|
|
|
|
|
**追问**
|
|
**追问**
|
|
|
|
|
+
|
|
|
1. 为什么 USB 摄像头常不支持 RGB565?(UVC 标准输出以 YUYV/MJPEG 为主)
|
|
1. 为什么 USB 摄像头常不支持 RGB565?(UVC 标准输出以 YUYV/MJPEG 为主)
|
|
|
2. `fmt.fmt.pix.sizeimage` 有什么用?(一帧图像数据的总字节数,用于分配缓冲/校验)
|
|
2. `fmt.fmt.pix.sizeimage` 有什么用?(一帧图像数据的总字节数,用于分配缓冲/校验)
|
|
|
|
|
|
|
@@ -2300,6 +2318,7 @@ tcsetattr(fd, TCSANOW, &new_cfg);
|
|
|
原始模式会清除 `ICANON/ECHO/ISIG` 等本地标志,并关闭 `OPOST` 输出处理——这是二进制通信必需的。
|
|
原始模式会清除 `ICANON/ECHO/ISIG` 等本地标志,并关闭 `OPOST` 输出处理——这是二进制通信必需的。
|
|
|
|
|
|
|
|
**追问**
|
|
**追问**
|
|
|
|
|
+
|
|
|
1. `VMIN=VTIME=0` 时 `read()` 行为?(立即返回:有数据返回字节数,无数据返回 0)
|
|
1. `VMIN=VTIME=0` 时 `read()` 行为?(立即返回:有数据返回字节数,无数据返回 0)
|
|
|
2. 为什么打开串口要加 `O_NOCTTY`?(避免该终端成为本进程的控制终端)
|
|
2. 为什么打开串口要加 `O_NOCTTY`?(避免该终端成为本进程的控制终端)
|
|
|
|
|
|
|
@@ -2310,6 +2329,7 @@ tcsetattr(fd, TCSANOW, &new_cfg);
|
|
|
**详细解答**:以 16bit 双声道为例,一帧 = 16/8 × 2 = 4 字节。`period_size=1024` 帧即 4096 字节;`periods=16` 则 buffer = 16×1024 帧。DMA 每搬完一个 period 触发一次中断。整块搬运延迟大,period 太小则中断频繁、CPU 开销高。`buf_bytes = period_size × BlockAlign` 即每次 `snd_pcm_writei/readi` 的数据量。
|
|
**详细解答**:以 16bit 双声道为例,一帧 = 16/8 × 2 = 4 字节。`period_size=1024` 帧即 4096 字节;`periods=16` 则 buffer = 16×1024 帧。DMA 每搬完一个 period 触发一次中断。整块搬运延迟大,period 太小则中断频繁、CPU 开销高。`buf_bytes = period_size × BlockAlign` 即每次 `snd_pcm_writei/readi` 的数据量。
|
|
|
|
|
|
|
|
**追问**
|
|
**追问**
|
|
|
|
|
+
|
|
|
1. overrun 和 underrun 分别发生在播放还是录音?(录音读太慢 → overrun;播放写太慢 → underrun,统称 XRUN)
|
|
1. overrun 和 underrun 分别发生在播放还是录音?(录音读太慢 → overrun;播放写太慢 → underrun,统称 XRUN)
|
|
|
2. 发生 XRUN 后如何恢复?(`snd_pcm_prepare()` 回到 PREPARED,或 `snd_pcm_drop()` 停止)
|
|
2. 发生 XRUN 后如何恢复?(`snd_pcm_prepare()` 回到 PREPARED,或 `snd_pcm_drop()` 停止)
|
|
|
|
|
|
|
@@ -2320,6 +2340,7 @@ tcsetattr(fd, TCSANOW, &new_cfg);
|
|
|
**详细解答**:阻塞方式下,录音无数据可读、播放缓冲满时调用会阻塞。错误码:`-EBADFD`(状态不对,需 PREPARED/RUNNING)、`-EPIPE`(XRUN,用 `prepare` 恢复)、`-ESTRPIPE`(硬件挂起,用 `resume` 或 `prepare`)。写入帧数小于请求值时,需回退文件读位置重发,见 `pcm_playback.c` 的 `lseek(fd, (ret-period_size)*BlockAlign, SEEK_CUR)`。
|
|
**详细解答**:阻塞方式下,录音无数据可读、播放缓冲满时调用会阻塞。错误码:`-EBADFD`(状态不对,需 PREPARED/RUNNING)、`-EPIPE`(XRUN,用 `prepare` 恢复)、`-ESTRPIPE`(硬件挂起,用 `resume` 或 `prepare`)。写入帧数小于请求值时,需回退文件读位置重发,见 `pcm_playback.c` 的 `lseek(fd, (ret-period_size)*BlockAlign, SEEK_CUR)`。
|
|
|
|
|
|
|
|
**追问**
|
|
**追问**
|
|
|
|
|
+
|
|
|
1. 非交错模式该用什么函数?(`snd_pcm_readn` / `snd_pcm_writen`)
|
|
1. 非交错模式该用什么函数?(`snd_pcm_readn` / `snd_pcm_writen`)
|
|
|
2. 为什么 `snd_pcm_hw_params()` 后设备就是 PREPARED?(其内部自动调用 `snd_pcm_prepare()`)
|
|
2. 为什么 `snd_pcm_hw_params()` 后设备就是 PREPARED?(其内部自动调用 `snd_pcm_prepare()`)
|
|
|
|
|
|