mirror of
https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
synced 2025-11-28 02:51:06 +08:00
发布:[03-1]_回顾与编写驱动框架,[03-2]_虚拟摄像头驱动编程_ioctl代码,[03-3]_虚拟摄像头驱动编程_buffer代码
This commit is contained in:
@@ -508,7 +508,3 @@ APP ioctl VIDIOC_DQBUF
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 2. 从0编写一个虚拟的摄像头驱动
|
||||
|
||||
|
||||
102
IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md
Normal file
102
IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# 从0编写一个虚拟摄像头驱动 #
|
||||
|
||||
参考资料:
|
||||
|
||||
* 深入理解linux内核v4l2框架之videobuf2:https://blog.csdn.net/yyjsword/article/details/9243717
|
||||
* V4L2框架-videobuf2:https://blog.csdn.net/u013904227/article/details/81054611
|
||||
|
||||
|
||||
|
||||
## 1. 回顾与编写程序框架
|
||||
|
||||
本节视频对应的代码:
|
||||
|
||||
```shell
|
||||
doc_and_source_for_drivers\
|
||||
IMX6ULL\source\
|
||||
13_V4L2\
|
||||
06_virtual_driver\
|
||||
01_virtual_driver_framework
|
||||
```
|
||||
|
||||
|
||||
|
||||
参考《1.3.2 videobuffer2的3个ops》里面完整的注册流程。
|
||||
|
||||
完整的注册流程(参考`drivers\media\usb\airspy\airspy.c`):
|
||||
|
||||
```shell
|
||||
static struct video_device airspy_template = {
|
||||
.name = "AirSpy SDR",
|
||||
.release = video_device_release_empty,
|
||||
.fops = &airspy_fops,
|
||||
.ioctl_ops = &airspy_ioctl_ops,
|
||||
};
|
||||
|
||||
/* 构造一个vb2_queue */
|
||||
/* Init videobuf2 queue structure */
|
||||
s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
|
||||
s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
|
||||
s->vb_queue.drv_priv = s;
|
||||
s->vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf);
|
||||
s->vb_queue.ops = &airspy_vb2_ops; // vb2_ops, 硬件相关的操作函数
|
||||
s->vb_queue.mem_ops = &vb2_vmalloc_memops; // vb2_mem_ops, 辅助结构体,用于mem ops(alloc、mmap)
|
||||
s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
ret = vb2_queue_init(&s->vb_queue);
|
||||
q->buf_ops = &v4l2_buf_ops; // vb2_buf_ops, 用于APP和驱动传递参数
|
||||
|
||||
// 分配/设置video_device结构体
|
||||
s->vdev = airspy_template;
|
||||
s->vdev.queue = &s->vb_queue; // 指向前面构造的vb2_queue
|
||||
|
||||
// 初始化一个v4l2_device结构体(起辅助作用)
|
||||
/* Register the v4l2_device structure */
|
||||
s->v4l2_dev.release = airspy_video_release;
|
||||
ret = v4l2_device_register(&intf->dev, &s->v4l2_dev);
|
||||
|
||||
// video_device和4l2_device建立联系
|
||||
s->vdev.v4l2_dev = &s->v4l2_dev;
|
||||
|
||||
// 注册video_device结构体
|
||||
ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1);
|
||||
__video_register_device
|
||||
// 根据次设备号把video_device结构体放入数组
|
||||
video_device[vdev->minor] = vdev;
|
||||
|
||||
// 注册字符设备驱动程序
|
||||
vdev->cdev->ops = &v4l2_fops;
|
||||
vdev->cdev->owner = owner;
|
||||
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 2. 编写硬件相关代码
|
||||
|
||||
本节视频对应的代码:
|
||||
|
||||
```shell
|
||||
doc_and_source_for_drivers\
|
||||
IMX6ULL\source\
|
||||
13_V4L2\
|
||||
06_virtual_driver\
|
||||
02_virtual_driver_hardware
|
||||
```
|
||||
|
||||
### 2.1 ioctl相关的代码
|
||||
|
||||
|
||||
|
||||
### 2.2 buffer相关的代码
|
||||
|
||||
|
||||
|
||||
### 2.3 数据传输
|
||||
|
||||
|
||||
|
||||
## 3. 上机测试
|
||||
|
||||
|
||||
|
||||
BIN
IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif
Normal file
BIN
IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif
Normal file
Binary file not shown.
@@ -0,0 +1,128 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
#include <media/videobuf2-vmalloc.h>
|
||||
|
||||
static const struct v4l2_file_operations virtual_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 virtual_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,
|
||||
|
||||
.vidioc_streamon = vb2_ioctl_streamon,
|
||||
.vidioc_streamoff = vb2_ioctl_streamoff,
|
||||
|
||||
.vidioc_g_tuner = airspy_g_tuner,
|
||||
.vidioc_s_tuner = airspy_s_tuner,
|
||||
|
||||
.vidioc_g_frequency = airspy_g_frequency,
|
||||
.vidioc_s_frequency = airspy_s_frequency,
|
||||
.vidioc_enum_freq_bands = airspy_enum_freq_bands,
|
||||
|
||||
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
|
||||
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
|
||||
.vidioc_log_status = v4l2_ctrl_log_status,
|
||||
};
|
||||
|
||||
static struct video_device g_vdev = {
|
||||
.name = "100ask_virtual_video",
|
||||
.release = video_device_release_empty,
|
||||
.fops = &virtual_fops,
|
||||
.ioctl_ops = &virtual_ioctl_ops,
|
||||
};
|
||||
static struct v4l2_device g_v4l2_dev;
|
||||
|
||||
static struct vb2_queue g_vb_queue;
|
||||
|
||||
static const struct vb2_ops virtul_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,
|
||||
};
|
||||
|
||||
|
||||
static int virtual_video_drv_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* 分配/设置/注册video_device */
|
||||
|
||||
/* 设置
|
||||
* 1. 函数调用(比如ioctl)
|
||||
* 2. 队列/buffer的管理
|
||||
*/
|
||||
|
||||
g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
|
||||
g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
|
||||
g_vb_queue.drv_priv = NULL;
|
||||
g_vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf);
|
||||
g_vb_queue.ops = &virtul_vb2_ops;
|
||||
g_vb_queue.mem_ops = &vb2_vmalloc_memops;
|
||||
g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
ret = vb2_queue_init(&s->vb_queue);
|
||||
if (ret) {
|
||||
printk("Could not initialize vb2 queue\n");
|
||||
return -1;;
|
||||
}
|
||||
|
||||
g_vdev.queue = &g_vb_queue;
|
||||
g_vdev.queue->lock = &s->vb_queue_lock;
|
||||
|
||||
/* Register the v4l2_device structure(辅助作用) */
|
||||
g_v4l2_dev.release = airspy_video_release;
|
||||
ret = v4l2_device_register(NULL, &g_v4l2_dev);
|
||||
if (ret) {
|
||||
printk("Failed to register v4l2-device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
g_vdev.v4l2_dev = &g_v4l2_dev;
|
||||
g_vdev.lock = &s->v4l2_lock;
|
||||
|
||||
ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1);
|
||||
if (ret) {
|
||||
printk("Failed to register as video device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtual_video_drv_exit(void)
|
||||
{
|
||||
/* 反注册/释放video_device */
|
||||
v4l2_device_unregister(&g_v4l2_dev);
|
||||
video_unregister_device(&g_vdev);
|
||||
}
|
||||
|
||||
module_init(virtual_video_drv_init);
|
||||
module_exit(virtual_video_drv_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
#include <media/videobuf2-vmalloc.h>
|
||||
|
||||
static struct list_head g_queued_bufs;
|
||||
|
||||
/* intermediate buffers with raw data from the USB device */
|
||||
struct virtual_frame_buf {
|
||||
/* common v4l buffer stuff -- must be first */
|
||||
struct vb2_v4l2_buffer vb;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static int virtual_querycap(struct file *file, void *fh,
|
||||
struct v4l2_capability *cap)
|
||||
{
|
||||
strlcpy(cap->driver, "100ask_virtual_video", sizeof(cap->driver));
|
||||
strlcpy(cap->card, "no-card", sizeof(cap->card));
|
||||
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE ;
|
||||
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virtual_enum_fmt_cap(struct file *file, void *priv,
|
||||
struct v4l2_fmtdesc *f)
|
||||
{
|
||||
if (f->index > 0)
|
||||
return -EINVAL;
|
||||
|
||||
strlcpy(f->description, "100ask motion jpeg", sizeof(f->description));
|
||||
f->pixelformat = V4L2_PIX_FMT_MJPEG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int virtual_s_fmt_cap(struct file *file, void *priv,
|
||||
struct v4l2_format *f)
|
||||
{
|
||||
/* 分辨用户传入的参数是否可用
|
||||
* 如果不可用, 给APP提供最接近的、硬件支持的参数
|
||||
*/
|
||||
|
||||
if (f.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
||||
return -EINVAL;
|
||||
if (f.fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG)
|
||||
return -EINVAL;
|
||||
|
||||
f.fmt.pix.width = 800;
|
||||
f.fmt.pix.height = 600;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virtual_enum_framesizes(struct file *file, void *fh,
|
||||
struct v4l2_frmsizeenum *fsize)
|
||||
{
|
||||
if (fsize->index > 0)
|
||||
return -EINVAL;
|
||||
|
||||
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
||||
fsize->discrete.width = 800;
|
||||
fsize->discrete.height = 600;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct v4l2_file_operations virtual_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 virtual_ioctl_ops = {
|
||||
.vidioc_querycap = virtual_querycap,
|
||||
|
||||
.vidioc_enum_fmt_vid_cap = virtual_enum_fmt_cap,
|
||||
.vidioc_s_fmt_vid_cap = virtual_s_fmt_cap,
|
||||
.vidioc_enum_framesizes = virtual_enum_framesizes,
|
||||
|
||||
.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,
|
||||
|
||||
.vidioc_streamon = vb2_ioctl_streamon,
|
||||
.vidioc_streamoff = vb2_ioctl_streamoff,
|
||||
};
|
||||
|
||||
static struct video_device g_vdev = {
|
||||
.name = "100ask_virtual_video",
|
||||
.release = video_device_release_empty,
|
||||
.fops = &virtual_fops,
|
||||
.ioctl_ops = &virtual_ioctl_ops,
|
||||
};
|
||||
static struct v4l2_device g_v4l2_dev;
|
||||
|
||||
static struct vb2_queue g_vb_queue;
|
||||
|
||||
/* Videobuf2 operations */
|
||||
static int virtual_queue_setup(struct vb2_queue *vq,
|
||||
unsigned int *nbuffers,
|
||||
unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
|
||||
{
|
||||
/* 假装:至少需要8个buffer, 每个buffer只有1个plane */
|
||||
|
||||
/* Need at least 8 buffers */
|
||||
if (vq->num_buffers + *nbuffers < 8)
|
||||
*nbuffers = 8 - vq->num_buffers;
|
||||
*nplanes = 1;
|
||||
sizes[0] = PAGE_ALIGN(800*600*2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void virtual_buf_queue(struct vb2_buffer *vb)
|
||||
{
|
||||
|
||||
/* 把这个buffer告诉硬件相关的驱动程序 */
|
||||
|
||||
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
||||
struct virtual_frame_buf *buf =
|
||||
container_of(vbuf, struct virtual_frame_buf, vb);
|
||||
unsigned long flags;
|
||||
|
||||
//spin_lock_irqsave(&s->queued_bufs_lock, flags);
|
||||
list_add_tail(&buf->list, &g_queued_bufs);
|
||||
//spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
|
||||
}
|
||||
|
||||
static int virtual_start_streaming(struct vb2_queue *vq, unsigned int count)
|
||||
{
|
||||
/* 启动硬件传输 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtual_stop_streaming(struct vb2_queue *vq)
|
||||
{
|
||||
/* 停止硬件传输 */
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
static int virtual_video_drv_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* 分配/设置/注册video_device */
|
||||
|
||||
/* 设置
|
||||
* 1. 函数调用(比如ioctl)
|
||||
* 2. 队列/buffer的管理
|
||||
*/
|
||||
|
||||
g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
|
||||
g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
|
||||
g_vb_queue.drv_priv = NULL;
|
||||
g_vb_queue.buf_struct_size = sizeof(struct virtual_frame_buf); /* 分配vb时, 分配的空间大小为buf_struct_size */
|
||||
g_vb_queue.ops = &virtul_vb2_ops;
|
||||
g_vb_queue.mem_ops = &vb2_vmalloc_memops;
|
||||
g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
ret = vb2_queue_init(&s->vb_queue);
|
||||
if (ret) {
|
||||
printk("Could not initialize vb2 queue\n");
|
||||
return -1;;
|
||||
}
|
||||
|
||||
g_vdev.queue = &g_vb_queue;
|
||||
g_vdev.queue->lock = &s->vb_queue_lock;
|
||||
|
||||
/* Register the v4l2_device structure(辅助作用) */
|
||||
g_v4l2_dev.release = airspy_video_release;
|
||||
ret = v4l2_device_register(NULL, &g_v4l2_dev);
|
||||
if (ret) {
|
||||
printk("Failed to register v4l2-device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
g_vdev.v4l2_dev = &g_v4l2_dev;
|
||||
g_vdev.lock = &s->v4l2_lock;
|
||||
|
||||
ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1);
|
||||
if (ret) {
|
||||
printk("Failed to register as video device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&g_queued_bufs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtual_video_drv_exit(void)
|
||||
{
|
||||
/* 反注册/释放video_device */
|
||||
v4l2_device_unregister(&g_v4l2_dev);
|
||||
video_unregister_device(&g_vdev);
|
||||
}
|
||||
|
||||
module_init(virtual_video_drv_init);
|
||||
module_exit(virtual_video_drv_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
@@ -809,6 +809,14 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
|
||||
[02-5]_videobuffer2情景分析
|
||||
```
|
||||
|
||||
* 2023.09.04 发布"摄像头驱动"
|
||||
|
||||
```shell
|
||||
[03-1]_回顾与编写驱动框架
|
||||
[03-2]_虚拟摄像头驱动编程_ioctl代码
|
||||
[03-3]_虚拟摄像头驱动编程_buffer代码
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -508,7 +508,3 @@ APP ioctl VIDIOC_DQBUF
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 2. 从0编写一个虚拟的摄像头驱动
|
||||
|
||||
|
||||
102
STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md
Normal file
102
STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# 从0编写一个虚拟摄像头驱动 #
|
||||
|
||||
参考资料:
|
||||
|
||||
* 深入理解linux内核v4l2框架之videobuf2:https://blog.csdn.net/yyjsword/article/details/9243717
|
||||
* V4L2框架-videobuf2:https://blog.csdn.net/u013904227/article/details/81054611
|
||||
|
||||
|
||||
|
||||
## 1. 回顾与编写程序框架
|
||||
|
||||
本节视频对应的代码:
|
||||
|
||||
```shell
|
||||
doc_and_source_for_drivers\
|
||||
IMX6ULL\source\
|
||||
13_V4L2\
|
||||
06_virtual_driver\
|
||||
01_virtual_driver_framework
|
||||
```
|
||||
|
||||
|
||||
|
||||
参考《1.3.2 videobuffer2的3个ops》里面完整的注册流程。
|
||||
|
||||
完整的注册流程(参考`drivers\media\usb\airspy\airspy.c`):
|
||||
|
||||
```shell
|
||||
static struct video_device airspy_template = {
|
||||
.name = "AirSpy SDR",
|
||||
.release = video_device_release_empty,
|
||||
.fops = &airspy_fops,
|
||||
.ioctl_ops = &airspy_ioctl_ops,
|
||||
};
|
||||
|
||||
/* 构造一个vb2_queue */
|
||||
/* Init videobuf2 queue structure */
|
||||
s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
|
||||
s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
|
||||
s->vb_queue.drv_priv = s;
|
||||
s->vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf);
|
||||
s->vb_queue.ops = &airspy_vb2_ops; // vb2_ops, 硬件相关的操作函数
|
||||
s->vb_queue.mem_ops = &vb2_vmalloc_memops; // vb2_mem_ops, 辅助结构体,用于mem ops(alloc、mmap)
|
||||
s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
ret = vb2_queue_init(&s->vb_queue);
|
||||
q->buf_ops = &v4l2_buf_ops; // vb2_buf_ops, 用于APP和驱动传递参数
|
||||
|
||||
// 分配/设置video_device结构体
|
||||
s->vdev = airspy_template;
|
||||
s->vdev.queue = &s->vb_queue; // 指向前面构造的vb2_queue
|
||||
|
||||
// 初始化一个v4l2_device结构体(起辅助作用)
|
||||
/* Register the v4l2_device structure */
|
||||
s->v4l2_dev.release = airspy_video_release;
|
||||
ret = v4l2_device_register(&intf->dev, &s->v4l2_dev);
|
||||
|
||||
// video_device和4l2_device建立联系
|
||||
s->vdev.v4l2_dev = &s->v4l2_dev;
|
||||
|
||||
// 注册video_device结构体
|
||||
ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1);
|
||||
__video_register_device
|
||||
// 根据次设备号把video_device结构体放入数组
|
||||
video_device[vdev->minor] = vdev;
|
||||
|
||||
// 注册字符设备驱动程序
|
||||
vdev->cdev->ops = &v4l2_fops;
|
||||
vdev->cdev->owner = owner;
|
||||
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 2. 编写硬件相关代码
|
||||
|
||||
本节视频对应的代码:
|
||||
|
||||
```shell
|
||||
doc_and_source_for_drivers\
|
||||
IMX6ULL\source\
|
||||
13_V4L2\
|
||||
06_virtual_driver\
|
||||
02_virtual_driver_hardware
|
||||
```
|
||||
|
||||
### 2.1 ioctl相关的代码
|
||||
|
||||
|
||||
|
||||
### 2.2 buffer相关的代码
|
||||
|
||||
|
||||
|
||||
### 2.3 数据传输
|
||||
|
||||
|
||||
|
||||
## 3. 上机测试
|
||||
|
||||
|
||||
|
||||
BIN
STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif
Normal file
BIN
STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif
Normal file
Binary file not shown.
@@ -0,0 +1,128 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
#include <media/videobuf2-vmalloc.h>
|
||||
|
||||
static const struct v4l2_file_operations virtual_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 virtual_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,
|
||||
|
||||
.vidioc_streamon = vb2_ioctl_streamon,
|
||||
.vidioc_streamoff = vb2_ioctl_streamoff,
|
||||
|
||||
.vidioc_g_tuner = airspy_g_tuner,
|
||||
.vidioc_s_tuner = airspy_s_tuner,
|
||||
|
||||
.vidioc_g_frequency = airspy_g_frequency,
|
||||
.vidioc_s_frequency = airspy_s_frequency,
|
||||
.vidioc_enum_freq_bands = airspy_enum_freq_bands,
|
||||
|
||||
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
|
||||
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
|
||||
.vidioc_log_status = v4l2_ctrl_log_status,
|
||||
};
|
||||
|
||||
static struct video_device g_vdev = {
|
||||
.name = "100ask_virtual_video",
|
||||
.release = video_device_release_empty,
|
||||
.fops = &virtual_fops,
|
||||
.ioctl_ops = &virtual_ioctl_ops,
|
||||
};
|
||||
static struct v4l2_device g_v4l2_dev;
|
||||
|
||||
static struct vb2_queue g_vb_queue;
|
||||
|
||||
static const struct vb2_ops virtul_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,
|
||||
};
|
||||
|
||||
|
||||
static int virtual_video_drv_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* 分配/设置/注册video_device */
|
||||
|
||||
/* 设置
|
||||
* 1. 函数调用(比如ioctl)
|
||||
* 2. 队列/buffer的管理
|
||||
*/
|
||||
|
||||
g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
|
||||
g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
|
||||
g_vb_queue.drv_priv = NULL;
|
||||
g_vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf);
|
||||
g_vb_queue.ops = &virtul_vb2_ops;
|
||||
g_vb_queue.mem_ops = &vb2_vmalloc_memops;
|
||||
g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
ret = vb2_queue_init(&s->vb_queue);
|
||||
if (ret) {
|
||||
printk("Could not initialize vb2 queue\n");
|
||||
return -1;;
|
||||
}
|
||||
|
||||
g_vdev.queue = &g_vb_queue;
|
||||
g_vdev.queue->lock = &s->vb_queue_lock;
|
||||
|
||||
/* Register the v4l2_device structure(辅助作用) */
|
||||
g_v4l2_dev.release = airspy_video_release;
|
||||
ret = v4l2_device_register(NULL, &g_v4l2_dev);
|
||||
if (ret) {
|
||||
printk("Failed to register v4l2-device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
g_vdev.v4l2_dev = &g_v4l2_dev;
|
||||
g_vdev.lock = &s->v4l2_lock;
|
||||
|
||||
ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1);
|
||||
if (ret) {
|
||||
printk("Failed to register as video device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtual_video_drv_exit(void)
|
||||
{
|
||||
/* 反注册/释放video_device */
|
||||
v4l2_device_unregister(&g_v4l2_dev);
|
||||
video_unregister_device(&g_vdev);
|
||||
}
|
||||
|
||||
module_init(virtual_video_drv_init);
|
||||
module_exit(virtual_video_drv_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
#include <media/videobuf2-vmalloc.h>
|
||||
|
||||
static struct list_head g_queued_bufs;
|
||||
|
||||
/* intermediate buffers with raw data from the USB device */
|
||||
struct virtual_frame_buf {
|
||||
/* common v4l buffer stuff -- must be first */
|
||||
struct vb2_v4l2_buffer vb;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static int virtual_querycap(struct file *file, void *fh,
|
||||
struct v4l2_capability *cap)
|
||||
{
|
||||
strlcpy(cap->driver, "100ask_virtual_video", sizeof(cap->driver));
|
||||
strlcpy(cap->card, "no-card", sizeof(cap->card));
|
||||
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE ;
|
||||
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virtual_enum_fmt_cap(struct file *file, void *priv,
|
||||
struct v4l2_fmtdesc *f)
|
||||
{
|
||||
if (f->index > 0)
|
||||
return -EINVAL;
|
||||
|
||||
strlcpy(f->description, "100ask motion jpeg", sizeof(f->description));
|
||||
f->pixelformat = V4L2_PIX_FMT_MJPEG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int virtual_s_fmt_cap(struct file *file, void *priv,
|
||||
struct v4l2_format *f)
|
||||
{
|
||||
/* 分辨用户传入的参数是否可用
|
||||
* 如果不可用, 给APP提供最接近的、硬件支持的参数
|
||||
*/
|
||||
|
||||
if (f.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
||||
return -EINVAL;
|
||||
if (f.fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG)
|
||||
return -EINVAL;
|
||||
|
||||
f.fmt.pix.width = 800;
|
||||
f.fmt.pix.height = 600;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virtual_enum_framesizes(struct file *file, void *fh,
|
||||
struct v4l2_frmsizeenum *fsize)
|
||||
{
|
||||
if (fsize->index > 0)
|
||||
return -EINVAL;
|
||||
|
||||
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
||||
fsize->discrete.width = 800;
|
||||
fsize->discrete.height = 600;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct v4l2_file_operations virtual_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 virtual_ioctl_ops = {
|
||||
.vidioc_querycap = virtual_querycap,
|
||||
|
||||
.vidioc_enum_fmt_vid_cap = virtual_enum_fmt_cap,
|
||||
.vidioc_s_fmt_vid_cap = virtual_s_fmt_cap,
|
||||
.vidioc_enum_framesizes = virtual_enum_framesizes,
|
||||
|
||||
.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,
|
||||
|
||||
.vidioc_streamon = vb2_ioctl_streamon,
|
||||
.vidioc_streamoff = vb2_ioctl_streamoff,
|
||||
};
|
||||
|
||||
static struct video_device g_vdev = {
|
||||
.name = "100ask_virtual_video",
|
||||
.release = video_device_release_empty,
|
||||
.fops = &virtual_fops,
|
||||
.ioctl_ops = &virtual_ioctl_ops,
|
||||
};
|
||||
static struct v4l2_device g_v4l2_dev;
|
||||
|
||||
static struct vb2_queue g_vb_queue;
|
||||
|
||||
/* Videobuf2 operations */
|
||||
static int virtual_queue_setup(struct vb2_queue *vq,
|
||||
unsigned int *nbuffers,
|
||||
unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
|
||||
{
|
||||
/* 假装:至少需要8个buffer, 每个buffer只有1个plane */
|
||||
|
||||
/* Need at least 8 buffers */
|
||||
if (vq->num_buffers + *nbuffers < 8)
|
||||
*nbuffers = 8 - vq->num_buffers;
|
||||
*nplanes = 1;
|
||||
sizes[0] = PAGE_ALIGN(800*600*2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void virtual_buf_queue(struct vb2_buffer *vb)
|
||||
{
|
||||
|
||||
/* 把这个buffer告诉硬件相关的驱动程序 */
|
||||
|
||||
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
||||
struct virtual_frame_buf *buf =
|
||||
container_of(vbuf, struct virtual_frame_buf, vb);
|
||||
unsigned long flags;
|
||||
|
||||
//spin_lock_irqsave(&s->queued_bufs_lock, flags);
|
||||
list_add_tail(&buf->list, &g_queued_bufs);
|
||||
//spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
|
||||
}
|
||||
|
||||
static int virtual_start_streaming(struct vb2_queue *vq, unsigned int count)
|
||||
{
|
||||
/* 启动硬件传输 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtual_stop_streaming(struct vb2_queue *vq)
|
||||
{
|
||||
/* 停止硬件传输 */
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
static int virtual_video_drv_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* 分配/设置/注册video_device */
|
||||
|
||||
/* 设置
|
||||
* 1. 函数调用(比如ioctl)
|
||||
* 2. 队列/buffer的管理
|
||||
*/
|
||||
|
||||
g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
|
||||
g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
|
||||
g_vb_queue.drv_priv = NULL;
|
||||
g_vb_queue.buf_struct_size = sizeof(struct virtual_frame_buf); /* 分配vb时, 分配的空间大小为buf_struct_size */
|
||||
g_vb_queue.ops = &virtul_vb2_ops;
|
||||
g_vb_queue.mem_ops = &vb2_vmalloc_memops;
|
||||
g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
ret = vb2_queue_init(&s->vb_queue);
|
||||
if (ret) {
|
||||
printk("Could not initialize vb2 queue\n");
|
||||
return -1;;
|
||||
}
|
||||
|
||||
g_vdev.queue = &g_vb_queue;
|
||||
g_vdev.queue->lock = &s->vb_queue_lock;
|
||||
|
||||
/* Register the v4l2_device structure(辅助作用) */
|
||||
g_v4l2_dev.release = airspy_video_release;
|
||||
ret = v4l2_device_register(NULL, &g_v4l2_dev);
|
||||
if (ret) {
|
||||
printk("Failed to register v4l2-device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
g_vdev.v4l2_dev = &g_v4l2_dev;
|
||||
g_vdev.lock = &s->v4l2_lock;
|
||||
|
||||
ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1);
|
||||
if (ret) {
|
||||
printk("Failed to register as video device (%d)\n", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&g_queued_bufs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtual_video_drv_exit(void)
|
||||
{
|
||||
/* 反注册/释放video_device */
|
||||
v4l2_device_unregister(&g_v4l2_dev);
|
||||
video_unregister_device(&g_vdev);
|
||||
}
|
||||
|
||||
module_init(virtual_video_drv_init);
|
||||
module_exit(virtual_video_drv_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user