参考： https://blog.csdn.net/hunanchenxingyu/article/details/47003279
1. shell 单行注释使用 “#” 就可以了，多行注释： :<<EOF EOF 这个 EOF 可以换成其他，都可以，只要上下能够对应就可以。 参考：https://blog.csdn.net/lansesl2008/article/details/20558369 https://blog.csdn.net/tenfyguo/article/details/5753548 2. 检查文件是否存在： if [ -f trials ]; then rm trials fi 参考： https://www.jb51.net/article/186273.htm 3. 退出脚本 exit 1 参考: https://blog.csdn.net/qq_35769944/article/details/81943543 4. 变量养成使用 {} 包起来的习惯 参考: https://www.runoob.com/linux/linux-shell-variable.html 5. 需要 python 脚本的时候，直接使用即可。 python3 test.py 参考： https://blog.csdn.net/zwt0909/article/details/52368559 6. -eq 更多用于数值的比较，字符串比较使用 == 参考： https://www.jb51.net/article/56559.htm 7. 与逻辑： && 或者 -a，或逻辑: || 或者 -o 参考: https://blog.csdn.net/iamlihongwei/article/details/59114828 https://www.cnblogs.com/aaronLinux/p/8340281.html 8. 判断命令行参数缺失 if [ -z $1 ];then echo "You should input one parameter" fi 参考： https://blog.csdn.net/geoffreychan/article/details/77823505 9. ssh 远程执行命令 ssh需要直接用密码执行命令需要在本地安装 sshpass sudo apt install sshpass ip="192.168.1.2" user="xyz" pswd="123456" sshpass -p ${pswd} ssh ${user}@${ip} "cd /home ; ls" 在远程执行的命令需要用双引号包含起来，不同命令需要用分号隔开。 或者类似下面也可以 #!/bin/bash ssh user@remoteNode > /dev/null 2>&1 << remotessh cd /home touch abcdefg.txt exit remotessh echo done! << remotessh 和 remotessh 之间的内容在远程执行，别忘了用 exit 退出 ssh。重定向在于不显示远程输出。 参考： https://blog.csdn.net/jinking01/article/details/84386769 https://www.cnblogs.com/kaishirenshi/p/7921308.html 10. 自动输入密码 需要先安装 expect，然后使用 expect 命令 expect <<EXPECT_EOF spawn sudo smbpasswd -a xyz expect "New SMB password:" send "123456\r" expect "Retype new SMB password:" send "123456\r" expect eof EXPECT_EOF 或者： expect -c " spawn sudo smbpasswd -a xyz expect { \"New SMB password:\" {set timeout 300; send \"123456\r\";} \"Retype new SMB password:\" {send \"123456\r\"} } #expect eof" expect eof 是和 spawn 配对使用的。 在 bash 中直接用 expect，有两种用法，1. expect <<EXPECT_EOF EXPECT_EOF 这样配对使用； 2. expect -c " " 这样双引号之间使用。 {send "yes\r"; exp_continue;} 这样的用法，exp_continue 表示这个 expect 的 内容和 send 内容可以重复多次使用。 参考: https://www.cnblogs.com/old-path-white-cloud/p/11678517.html https://www.cnblogs.com/sharktech/p/7454767.html https://blog.csdn.net/zhongbeida_xue/article/details/78679549 https://blog.csdn.net/weixin_34354173/article/details/91895917 https://blog.csdn.net/zhangjikuan/article/details/51105166 https://www.cnblogs.com/lixigang/articles/4849527.html https://www.cnblogs.com/autopenguin/p/5918717.html https://www.cnblogs.com/taosim/articles/3785817.html https://www.linuxidc.com/Linux/2017-02/140365.htm https://developer.aliyun.com/article/512290 https://blog.csdn.net/dongqing27/article/details/83108877 11. yes 命令可以一直输入 yes，直到被 kill 掉。 mkdir test touch test/test.txt yes | rm -rf ./test kill -9 $(pidof yes) 类似于上面这种写法。 参考： https://blog.csdn.net/yasi_xi/article/details/8562002 https://www.linuxcool.com/yes https://man.linuxde.net/yes https://www.cnblogs.com/jins-note/p/9636969.html 12. patch 需要先安装 patch 包。 首先先把文件复制出来，修改，然后用 diff 生成 patch 文件 cp /etc/samba/smb.conf ./ vim smb.conf diff -c /etc/samba/smb.conf smb.conf > smb_conf.patch diff -c 选项是详细信息。 patch /etc/samba/smb.conf smb_conf.patch 参考: https://www.cnblogs.com/cute/archive/2011/04/29/2033011.html 13. 使用 scp 发送数据到远程 sshpass -p ${pswd} scp ~/bin/smb_conf.patch ${user}@${ip}:/home/${user} 参考: https://www.runoob.com/linux/linux-comm-scp.html 14. sudo 自动输入密码 echo "admin" | sudo -S service tomcat7 stop 参考: https://blog.csdn.net/wangbole/article/details/17579463 15. 代码块的结束符 EOF 之类的一定要顶格写，不要有空格，否则会报错，类似下面这样： chmod_file() { echo "chmod file" # ssh remote and exec command sshpass -p ${pswd} ssh ${user}@${ip} <<EOF chmod 400 ./.ssh/id_rsa chmod 644 ./.ssh/id_rsa.pub exit EOF } 16. 简单的 yes no免输入交互 echo "y" | rm /mnt/speech/.ssh/id_rsa* 17. 数组 array_name=(value1 value2 ... valuen) array_name=() array_name[0]=value0 array_name[1]=value1 array_name[2]=value2 echo "第一个元素为: ${array_name[0]}" echo "数组的元素为: ${array_name[*]}" echo "数组的元素为: ${array_name[@]}" echo "数组元素个数为: ${#array_name[*]}" echo "数组元素个数为: ${#array_name[@]}" 参考: https://www.runoob.com/linux/linux-shell-array.html 18. shell for 循环遍历数组 a=("Fdf" "df" "fd") for str in ${a[@]};do echo $str done 参考: https://blog.csdn.net/weiwjacsdn/article/details/80368666 19. 函数参数使用 $1, $2 这样的。 参考: https://www.runoob.com/linux/linux-shell-func.html 20. 字符串按照空格拆分为数组 STR="123 A1 A2" ARR=($STR) echo "${ARR[0]}:${ARR[1]}:${ARR[2]} 参考: https://blog.csdn.net/weixin_44687868/article/details/108379011 21. 数组作为参数传递，也必须使用 ${xyz[*]} 这样形式的。 参考： https://blog.csdn.net/mydriverc2/article/details/78931628 22. 数组作为参数通过命令行传递，两种方法，一种是直接多个参数，还有一种是使用 " 双引号把数组框起来。 参考: https://www.runoob.com/linux/linux-shell-passing-arguments.html 23. 判断是否以 某个字符开头，比如 [ 这样的数组。 if [[ $cases =~ ^\[.* ]]; then casesStr=`cat ${target_json} |jq .cases[]| sed 's/\"//g'` cases=($casesStr) else cases=($cases) fi echo "cases: ==> $cases" 参考: https://www.cnblogs.com/LiuYanYGZ/p/12491673.html 24. 从 json 文件中读取使用 jq 命令，获取数值的时候，如果只是字符串，那么使用 jq .abc 这样，如果是数组，那么使用 jq .abc[]。 cases=`cat ${target_json} |jq .cases| sed 's/\"//g'` casesStr=`cat ${target_json} |jq .cases[]| sed 's/\"//g'` 参考: https://www.cnblogs.com/nul1/p/12228785.html#3%E8%BE%93%E5%87%BA%E6%95%B0%E7%BB%84%E7%9A%84%E5%80%BC 25. find 控制搜索深度 使用 -maxdepth 这个参数, 使用 -type d 表明搜索的是目录。 find ./ -maxdepth 3 -type d 参考: https://blog.csdn.net/lmmcu_2012/article/details/52703395 26. sed 可以用来替换字符， s/old/new/g 这样的方式即可。 casesStr=`cat ${target_json} |jq .cases[]| sed 's/\"//g'` 参考: https://www.runoob.com/linux/linux-comm-sed.html
今天碰到一个情况，分辨率太大，导致左下角的菜单显示不出来，无法调整分辨率。 cd /usr/share/applications sudo lxrandr 就可以运行分辨率设置的程序了。
1. pcm 是 ADC 直接采集到的数据。 2. frame，帧，指的是对所有声道进行一次 ADC 转换得到数据。 3. frame size，指的是一帧 包含的字节数。 如果是单声道，8bit adc，那么就是 1 8 / 8 = 1. 如果是8声道，16bit adc，那么就是 8 16 / 8 = 16. 4. rate 采样率，一秒对所有通道进行多少轮的采样，也就是一秒多少帧 5. data rate，这个就是 采样率 * 每帧字节数 6. period，每次处理 AD，DA 转换的间隔是周期。 内核可以缓冲很多周期的数据，每个周期都会生成一个中断和一个副本，用户层可以间隔很长时间再处理一次，一次可以处理多个周期的数据。 7. period size 这个数值指的是一个周期包含多少帧。 如果设置为 32，那么用户每次读写都是 32帧数据，或者0帧数据。 参考： https://larsimmisch.github.io/pyalsaaudio/terminology.html
python 中 需要使用 messagequeue 需要 import posix_ipc，然后才能用。 python 版本的 messagequeue 是对 C 版本的封装。 具体参考： http://semanchuk.com/philip/posix_ipc/#message_queue
linux 系统上配置 .gitconfig [core] autocrlf = input safecrlf = warn 配置为提交时转为 lf，下拉时不转换。如果文件时混合换行符时，进行警告。 参考：https://www.jianshu.com/p/6ef90ce18ba2 https://blog.csdn.net/whsdu929/article/details/52490188 http://kuanghy.github.io/2017/03/19/git-lf-or-crlf http://kuanghy.github.io/2017/03/19/git-lf-or-crlf
总结 数据采集流程 设置好长宽之后，摄像头可能不支持，需要检查长宽参数是否被修改。 app 根据自身处理能力的快慢，指定申请的 buffer 数量。 一般使用链表的形式来组织 buffer，app 从 out 链表中拿数据，拿完之后，放入 in 链表；驱动从 in 链表中拿到 buffer，然后填充数据，再放到 out 链表中去。 app 开发 关键结构体: v4l2_fmtdesc format enumeration v4l2_format stream data format ioctl 中常用的 id 是: VIDIOC_QUERYCTRL 查找参数 VIDIOC_G_CTRL 获取当前参数 VIDIOC_S_CTRL 设置当前参数 对于 VideoControl interface 内部，有多个 unit。 unit 自身只能多个输入，单个输出。 unit之间，只能单个 unit 输出给多个 unit. VideoControl interface 内部有 CT(camera terminal), IT(input terminal), OT(out terminal), SU(selector unit), PU(processing unit), EU(encoding unit), XU(extension unit) VideoStreaming interface 是 USB 相关 如果自己做摄像头，想要增加额外功能，那就需要用到 XU，并且还需要添加 mapping，指定具体哪些位是哪些功能。 app 帧细节 格式枚举(yuv 还是其他)，用 VIDIOC_ENUM_FMT; 帧枚举(分辨率之类)用 VIDIOC_ENUM_FRAMESIZES, 都需要让相应的 index 增加来通过 ioctl 枚举。 app 获取数据 检查设备能力 struct v4l2_capability cap; memset(&cap, 0, sizeof(struct v4l2_capability)); if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap)) { if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { 枚举格式和帧VIDIOC_ENUM_FMT VIDIOC_ENUM_FRAMESIZES 设置格式 struct v4l2_format fmt; memset(&fmt, 0, sizeof(struct v4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 1024; fmt.fmt.pix.height = 768; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; fmt.fmt.pix.field = V4L2_FIELD_ANY; if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt)) 申请buffer 并 mmap struct v4l2_requestbuffers rb; memset(&rb, 0, sizeof(struct v4l2_requestbuffers)); rb.count = 32; rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rb.memory = V4L2_MEMORY_MMAP; if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb)) { /* 申请成功后, mmap这些buffer */ buf_cnt = rb.count; for(i = 0; i < rb.count; i++) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) { /* mmap */ bufs[i] = mmap(0 /* start anywhere */ , buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if(bufs[i] == MAP_FAILED) { perror("Unable to map buffer"); return -1; } } 所有buffer放入"空闲链表" for(i = 0; i < buf_cnt; ++i) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (0 != ioctl(fd, VIDIOC_QBUF, &buf)) 启动摄像头 if (0 != ioctl(fd, VIDIOC_STREAMON, &type)) 使用 poll 从 buffer 获取数据，并写入文件，然后再放回队列 while (1) { /* poll */ memset(fds, 0, sizeof(fds)); fds[0].fd = fd; fds[0].events = POLLIN; if (1 == poll(fds, 1, -1)) { /* 把buffer取出队列 */ struct v4l2_buffer buf; memset(&buf, 0, sizeof(struct v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (0 != ioctl(fd, VIDIOC_DQBUF, &buf)) { perror("Unable to dequeue buffer"); return -1; } /* 把buffer的数据存为文件 */ sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++); int fd_file = open(filename, O_RDWR | O_CREAT, 0666); if (fd_file < 0) { printf("can not create file : %s\n", filename); } printf("capture to %s\n", filename); write(fd_file, bufs[buf.index], buf.bytesused); close(fd_file); /* 把buffer放入队列 */ if (0 != ioctl(fd, VIDIOC_QBUF, &buf)) { perror("Unable to queue buffer"); return -1; } } } 关闭摄像头 if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type)) app brightness 主线程获取图像数据，调整亮度另外开一个线程。 先用 v4l2_queryctrl V4L2_CID_BRIGHTNESS 获取 亮度范围，然后再用 VIDIOC_G_CTRL 获取当前亮度。 再用 getchar 获取按键，然后调整当前亮度数值，并用 VIDIOC_S_CTRL 来设置亮度。 v4l2 驱动框架 v4l2 接口，已经在 v4l2-dev.h 中定义好了，在 v4l2-dev.c 实现好了。 重要的结构体 struct video_device 以及其中的 const struct v4l2_file_operations *fops; 具体可以参考 drivers\media\usb\airspy\airspy.c 中的实现。 s->vdev = airspy_template; 赋值 video_device video_set_drvdata(&s->vdev, s); 把 s 赋值给 video_device 中的 device 里面的 driver_data 注册 v4l2_device s->v4l2_dev.release = airspy_video_release; ret = v4l2_device_register(&intf->dev, &s->v4l2_dev); s->vdev.v4l2_dev = &s->v4l2_dev; 把 v4l2_device 赋值给 video_device ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1); 注册 video_device vdev->cdev->ops = &v4l2_fops; 把 file_operations 赋值给 video_device v4l2 ioctl 调用流程分析 app 调用 ioctl, 就是 drivers\media\v4l2-core\v4l2-dev.c 里面的 v4l2_fops->v4l2_ioctl 调用 vdev->fops->unlocked_ioctl 这个来自 drivers\media\usb\airspy\airspy.c 的 airspy_fops->unlocked_ioctl 值是 video_ioctl2 这个函数。 video_ioctl2 video_usercopy(file, cmd, arg, __video_do_ioctl); __video_do_ioctl 调用了 info = &v4l2_ioctls[_IOC_NR(cmd)]; static struct v4l2_ioctl_info v4l2_ioctls[] = { IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0), IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)), IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0), IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO), IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE), IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)), IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf, v4l_print_framebuffer, 0), IOCTL_INFO_STD(VIDIOC_S_FBUF, vidioc_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO), 上面的 v4l2_ioctls 中含有 IOCTL_INFO_FNC 和 IOCTL_INFO_STD 两种。 struct v4l2_ioctl_info { unsigned int ioctl; u32 flags; const char * const name; union { u32 offset; int (*func)(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *p); } u; void (*debug)(const void *arg, bool write_only); }; #define IOCTL_INFO_STD(_ioctl, _vidioc, _debug, _flags) \ [_IOC_NR(_ioctl)] = { \ .ioctl = _ioctl, \ .flags = _flags | INFO_FL_STD, \ .name = #_ioctl, \ .u.offset = offsetof(struct v4l2_ioctl_ops, _vidioc), \ .debug = _debug, \ } #define IOCTL_INFO_FNC(_ioctl, _func, _debug, _flags) \ [_IOC_NR(_ioctl)] = { \ .ioctl = _ioctl, \ .flags = _flags | INFO_FL_FUNC, \ .name = #_ioctl, \ .u.func = _func, \ .debug = _debug, \ } 可以看出来，std 这种是在 struct v4l2_ioctl_ops 中的 offset 便宜，而 func 是直接用的赋值的函数。这个 std 对应的是: // drivers\media\usb\airspy\airspy.c static const struct v4l2_ioctl_ops airspy_ioctl_ops = { .vidioc_querycap = airspy_querycap, .vidioc_enum_fmt_sdr_cap = airspy_enum_fmt_sdr_cap, .vidioc_g_fmt_sdr_cap = airspy_g_fmt_sdr_cap, .vidioc_s_fmt_sdr_cap = airspy_s_fmt_sdr_cap, .vidioc_try_fmt_sdr_cap = airspy_try_fmt_sdr_cap, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_prepare_buf = vb2_ioctl_prepare_buf, .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, ... videobuffer2 结构体 airspy_ioctl_ops 中的 .vidioc_reqbufs = vb2_ioctl_reqbufs, int vb2_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count); allocated_buffers = __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes); 在 __vb2_queue_alloc 中用到了 vb2_queue vb2_buffer 等结构体。实际的 buffer 是 vb2_queue.bufs -> vb2_buffer.planes -> vb2_plane.mem_priv -> vb2_vmalloc_buf.vddr 指向真正的 buffer. 一般申请 buffer 之后，会用 ioctl(fd, VIDIOC_QBUF, &buf) 先把 buffer 挂在 vb2_queue.queued_list, 等到摄像头保存数据之后，就把 buffer 挂到 vb2_queue.done_list , 等到 app 读取完数据后，就可以用 ioctl(fd, VIDIOC_DQBUF, &buf) 从 done_list 拿数据之后，就可以用 ioctl(fd, VIDIOC_QBUF, &buf) 再把 buffer 挂到 queued_list 中。 videobuffer2 的 ops video_device 对应了两个 ops: static struct video_device airspy_template = { .name = "AirSpy SDR", .release = video_device_release_empty, .fops = &airspy_fops, .ioctl_ops = &airspy_ioctl_ops, }; vb2_queue 对应了三个 ops: s->vb_queue.ops = &airspy_vb2_ops; s->vb_queue.mem_ops = &vb2_vmalloc_memops; ret = vb2_queue_init(&s->vb_queue); q->buf_ops = &v4l2_buf_ops; 其中: app 和 驱动之间的 open,read 等操作，靠的是 video_device 中的： static const struct v4l2_file_operations airspy_fops static const struct v4l2_file_operations airspy_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vb2_fop_release, .read = vb2_fop_read, .poll = vb2_fop_poll, .mmap = vb2_fop_mmap, .unlocked_ioctl = video_ioctl2, }; static const struct v4l2_ioctl_ops airspy_ioctl_ops static const struct v4l2_ioctl_ops airspy_ioctl_ops = { .vidioc_querycap = airspy_querycap, .vidioc_enum_fmt_sdr_cap = airspy_enum_fmt_sdr_cap, .vidioc_g_fmt_sdr_cap = airspy_g_fmt_sdr_cap, .vidioc_s_fmt_sdr_cap = airspy_s_fmt_sdr_cap, .vidioc_try_fmt_sdr_cap = airspy_try_fmt_sdr_cap, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_prepare_buf = vb2_ioctl_prepare_buf, .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, ... app 和 驱动之间 buf 也就是 v4l2_buffer 和 vb2_buffer 之间，靠的是 vb2_ops， vb2_mem_ops， vb2_buf_ops。 struct vb2_buf_ops 中重要的是 fill_user_buffer 向用户 buf提供数据，fill_vb2_buffer 向驱动 buf 提供数据 // drivers\media\v4l2-core\videobuf2-v4l2.c static const struct vb2_buf_ops v4l2_buf_ops = { .verify_planes_array = __verify_planes_array_core, .fill_user_buffer = __fill_v4l2_buffer, .fill_vb2_buffer = __fill_vb2_buffer, .copy_timestamp = __copy_timestamp, }; struct vb2_mem_ops 主要用于内存辅助函数 // drivers\media\v4l2-core\videobuf2-vmalloc.c const struct vb2_mem_ops vb2_vmalloc_memops = { .alloc = vb2_vmalloc_alloc, .put = vb2_vmalloc_put, .get_userptr = vb2_vmalloc_get_userptr, .put_userptr = vb2_vmalloc_put_userptr, #ifdef CONFIG_HAS_DMA .get_dmabuf = vb2_vmalloc_get_dmabuf, #endif .map_dmabuf = vb2_vmalloc_map_dmabuf, .unmap_dmabuf = vb2_vmalloc_unmap_dmabuf, .attach_dmabuf = vb2_vmalloc_attach_dmabuf, .detach_dmabuf = vb2_vmalloc_detach_dmabuf, .vaddr = vb2_vmalloc_vaddr, .mmap = vb2_vmalloc_mmap, .num_users = vb2_vmalloc_num_users, }; struct vb2_ops 用于硬件操作相关的处理 // drivers\media\usb\airspy\airspy.c static const struct vb2_ops airspy_vb2_ops = { .queue_setup = airspy_queue_setup, .buf_queue = airspy_buf_queue, .start_streaming = airspy_start_streaming, .stop_streaming = airspy_stop_streaming, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, }; videobuffer2 情景分析 分配内存 app 通过 ioctl(fd, VIDIOC_REQBUFS, &rb) 调用了驱动中的 vb2_ioctl_reqbufs, .vidioc_reqbufs = vb2_ioctl_reqbufs, int vb2_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count); ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes, plane_sizes, q->alloc_devs); ... __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes); ret = __vb2_buf_mem_alloc(vb); mem_priv = call_ptr_memop(vb, alloc, q->alloc_devs[plane] ? : q->dev, q->dma_attrs, size, dma_dir, q->gfp_flags); #define call_qop(q, op, args...) \ ((q)->ops->op ? (q)->ops->op(args) : 0) #define call_ptr_memop(vb, op, args...) \ ((vb)->vb2_queue->mem_ops->op ? \ (vb)->vb2_queue->mem_ops->op(args) : NULL) call_qop 宏展开: vdev->queue->ops->queue_setup 调用了 struct vb2_ops 里面的 queue_setup call_ptr_memop 宏展开: vb->vb2_queue->mem_ops->alloc 调用了 struct vb2_mem_ops 里面的 alloc buffer 放入队列 app 通过 ioctl(fd, VIDIOC_QBUF, &buf) 调用了驱动中的 vb2_ioctl_qbuf, .vidioc_qbuf = vb2_ioctl_qbuf, int vb2_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) return vb2_qbuf(vdev->queue, p); vb2_core_qbuf(q, b->index, b); ret = __buf_prepare(vb, pb); ret = __qbuf_mmap(vb, pb); if (pb) ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, vb, pb, vb->planes); return ret ? ret : call_vb_qop(vb, buf_prepare, vb); list_add_tail(&vb->queued_entry, &q->queued_list); if (pb) call_void_bufop(q, copy_timestamp, vb, pb); if (q->start_streaming_called) __enqueue_in_driver(vb); for (plane = 0; plane < vb->num_planes; ++plane) call_void_memop(vb, prepare, vb->planes[plane].mem_priv); call_void_vb_qop(vb, buf_queue, vb); if (pb) call_void_bufop(q, fill_user_buffer, vb, pb); if (q->streaming && !q->start_streaming_called && q->queued_count >= q->min_buffers_needed) { ret = vb2_start_streaming(q); ... } list_for_each_entry(vb, &q->queued_list, queued_entry) __enqueue_in_driver(vb); for (plane = 0; plane < vb->num_planes; ++plane) call_void_memop(vb, prepare, vb->planes[plane].mem_priv); call_void_vb_qop(vb, buf_queue, vb); ret = call_qop(q, start_streaming, q, atomic_read(&q->owned_by_drv_count)); #define call_bufop(q, op, args...) \ ({ \ int ret = 0; \ if (q && q->buf_ops && q->buf_ops->op) \ ret = q->buf_ops->op(args); \ ret; \ }) #define call_vb_qop(vb, op, args...) \ ((vb)->vb2_queue->ops->op ? (vb)->vb2_queue->ops->op(args) : 0) #define call_void_memop(vb, op, args...) \ do { \ if ((vb)->vb2_queue->mem_ops->op) \ (vb)->vb2_queue->mem_ops->op(args); \ } while (0) #define call_void_bufop(q, op, args...) \ ({ \ if (q && q->buf_ops && q->buf_ops->op) \ q->buf_ops->op(args); \ }) #define call_qop(q, op, args...) \ ((q)->ops->op ? (q)->ops->op(args) : 0) 注: struct vb2_ops 是硬件相关的函数。 call_bufop 宏展开: vb->vb2_queue->buf_ops->fill_vb2_buffer 调用了 struct vb2_buf_ops 里面的 fill_vb2_buffer call_vb_qop 宏展开: vb->vb2_queue->ops->buf_prepare 调用了 struct vb2_ops 里面的 buf_prepare list_add_tail(&vb->queued_entry, &q->queued_list); vb2_buffer 加入到 vb2_queue call_bufop 宏展开: vb2_queue->buf_ops->copy_timestamp 调用了 struct vb2_buf_ops 里面的 copy_timestamp call_void_memop(vb, prepare, vb->planes[plane].mem_priv); 宏展开: vb->vb2_queue->mem_ops->prepare 调用了 struct vb2_mem_ops 里面的 prepare call_void_vb_qop(vb, buf_queue, vb); 宏展开: vb->vb2_queue->ops->buf_queue 调用了 struct vb2_ops 里面的 buf_queue call_void_bufop(q, fill_user_buffer, vb, pb); 宏展开: vb2_queue->buf_ops->fill_user_buffer 调用了 struct vb2_buf_ops 里面的 fill_user_buffer call_qop(q, start_streaming, q, atomic_read(&q->owned_by_drv_count)); 宏展开: vb2_queue->ops->start_streaming 调用了 struct vb2_ops 里面的 start_streaming 回顾与编写驱动框架 drivers\media\v4l2-core\v4l2-dev.c 中的 fops 是 file_operations 经过一路调用，最后到硬件相关的 v4l2_file_operations // drivers\media\v4l2-core\v4l2-dev.c static const struct file_operations v4l2_fops = { .owner = THIS_MODULE, .read = v4l2_read, .write = v4l2_write, .open = v4l2_open, .get_unmapped_area = v4l2_get_unmapped_area, .mmap = v4l2_mmap, .unlocked_ioctl = v4l2_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = v4l2_compat_ioctl32, #endif .release = v4l2_release, .poll = v4l2_poll, .llseek = no_llseek, }; // drivers\media\usb\airspy\airspy.c static const struct v4l2_file_operations airspy_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vb2_fop_release, .read = vb2_fop_read, .poll = vb2_fop_poll, .mmap = vb2_fop_mmap, .unlocked_ioctl = video_ioctl2, }; 有两个 device，通过 g_vdev.v4l2_dev = &g_v4l2_dev; 这样的方法，把 v4l2_device 赋值给 video_device 中的一个成员变量。注册的时候，先注册 v4l2_device 后注册 video_device ; 卸载 v4l2_device 需要先 s->v4l2_dev.release = airspy_video_release; 设置好 release，然后让内核自动调用。 一个是 struct video_device, 用 ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1); 注册 一个是 struct v4l2_device , 用 ret = v4l2_device_register(NULL, &g_v4l2_dev); 注册。 虚拟摄像头 ioctl 根据 app 前面使用到的 ioctl 来实现驱动中相关的函数。 ioctl(fd, VIDIOC_QUERYCAP, &cap) static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_querycap = virtual_querycap, 并在函数中配置 struct v4l2_capability ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_enum_fmt_vid_cap = virtual_enum_fmt_cap, 并在函数中配置 f->pixelformat = V4L2_PIX_FMT_MJPEG;，用于向 app 返回可用格式。 ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_enum_framesizes = virtual_enum_framesizes, 并在函数中配置 分辨率, 用于向 app 返回可用分辨率 ioctl(fd, VIDIOC_S_FMT, &fmt) static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_s_fmt_vid_cap = virtual_s_fmt_cap, 并在函数中配置从 app 传过来的参数，如果传过来的参数不可用，就返回可用参数。 其他 ioctl 其他 ioctl 中可以在 static const struct v4l2_ioctl_ops virtual_ioctl_ops 中配置为内核提供的 vb2_ioctl_ 开头的函数。 驱动 buff 代码 buffer 相关的三个结构体中，vb2_buf_ops 和 vb2_mem_ops 使用内核自带的函数，vb2_ops 使用的是硬件相关，需要开发人员提供函数。 static const struct vb2_ops virtul_vb2_ops = { .queue_setup = virtual_queue_setup, .buf_queue = virtual_buf_queue, .start_streaming = virtual_start_streaming, .stop_streaming = virtual_stop_streaming, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, }; queue_setup 用于检查用户需求的 buf 数量是否够用，驱动程序可以调整数据。并且把有些地址进行页面对其。 buf_queue 通过 container_of 从 vb2_buffer 找到 vb2_v4l2_buffer 再找到含有 list 的 airspy_frame_buf 通过 list_add_tail 把 airspy_frame_buf 挂载到 设备的 queued_bufs 下面。 驱动 传输相关 在 start_streaming 之后，会启动数据传输，然后在相关的中断函数中，去读取摄像头数据，并保存到 buffer 中，然后放到 done 的列表中，供 app 使用。 start_streaming 简单的设置并开始一个定时器，然后在定时器中进行具体处理。 virtual_timer_expire fbuf = airspy_get_next_fill_buf(s); 先实现这个函数，找到下一个可用的 buffer ptr = vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0); 从 下一个可用的 buffer 获得 写入地址。 len = airspy_convert_stream(s, ptr, urb->transfer_buffer, urb->actual_length); 把具体数据写入到 buffer 中。 vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, len); 设置前面写入 buffer 的长度。 vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_DONE); 把 buffer 放到 done list 中。 驱动 上级调试 可以参考 drivers\media\usb\airspy\airspy.c drivers\media\usb\uvc\uvc_driver.c 相关的 lock 还是需要提供，在 prepare 之类的时候，会被内核中其他函数使用。 ret = video_register_device(&g_vdev, VFL_TYPE_GRABBER, -1); 参数建议用 VFL_TYPE_GRABBER，创建设备的时候，是 /dev/video*, 如果是 VFL_TYPE_SDR，设备名称就是一个 sw 开头的。 v4l2_ioctl_ops 中的 vidioc_g_fmt_vid_cap 函数也需要实现，用于获取当前摄像头的 v4l2_pix_format stop_streaming 的实现函数中，需要把 queue 链表中的所有 buffer，全部通过 vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 这个操作一边，才能正常退出。 注: 可以在 app 中使用 perror 来打印出更多的信息。
1. 从 二进制 pcm 文件中读取数据，并转化位想要的矩阵数组 with open(audioPath, 'rb') as f: audioData = np.fromfile(f, dtype = np.uint16) audioData.shape = -1, 8 转换的音频数据是 不确定行，8列数组。 2. 把矩阵转置，以单声道数据为行。 audioData = audioData.T 转换为 8行的二维数组，每一行就是一个声道的数据。 3. 抽取一个通道的数据。 ch1 = audioData[4] 4. 把这个通道的数据写入二进制文件 ch1.tofile("./audio/ch1.pcm") 参考： https://blog.51cto.com/feature09/2316652 https://blog.csdn.net/Tourior/article/details/110791039 https://blog.csdn.net/kebu12345678/article/details/54837245 https://numpy.org/doc/stable/reference/generated/numpy.fromfile.html#:~:text=numpy.fromfile.%20%C2%B6.%20numpy.fromfile%28file%2C%20dtype%3Dfloat%2C%20count%3D-1%2C%20sep%3D%27%27%2C%20offset%3D0%29%20%C2%B6.,tofile%20method%20can%20be%20read%20using%20this%20function. https://www.cnblogs.com/peixu/p/7991715.html https://blog.csdn.net/botao_li/article/details/104378469 https://www.cnblogs.com/noluye/p/11224137.html
两种方法： 1. 启动的时候，使用命令启动 LC_CTYPE=zh_CN.UTF-8 emacs $@ 2. 设置 bashrc LC_CTYPE = zh_CN.utf8 或者 UTF-8 参考： https://www.douban.com/group/topic/29718313/
samba 共享的文件，被修改后，会被增加执行位。 要保留原来的属性，只需要修改 /etc/samba/smb.conf [global] ...... map archive = no 参考： https://stackoverflow.com/questions/20958888/preserving-file-permissions-for-samba-shares-when-file-is-edited https://en.wikipedia.org/wiki/Archive_bit https://www.linuxquestions.org/questions/linux-server-73/samba-adds-execute-flag-to-all-new-files-783263/ https://www.samba.org/samba/docs/using_samba/ch08.html