【V4L2】ioctl调用v4l2函数的简单分析(采集、编码)

1.Linux下使用ioctl()对V4L2设备进行控制

在Linux(Ubuntu)下,下载V4L2源码的方式用命令行实现,我下载的version是6.8.0。API的文档:Part I - Video for Linux API

sudo apt-get install linux-source
tar -xvf /usr/src/linux-source-<version>.tar.xz

在 Linux 中,ioctl(输入/输出控制)是一个系统调用,用于执行设备驱动程序提供的各种设备特定操作,使用ioctl()可以实现V4L2设备的控制,参考:
(1)v4l2-ctl基本使用方法
(2)V4L2 视频采集笔记
引用其中的一部分代码,可以看到ioctl()的使用方式

#include <linux/videodev2.h> // v4l2头文件

int main()
{
	int fd;
	struct v4l2_capability cap; // v4l2设备能力
    struct v4l2_format fmt;		// v4l2设备格式
	// ...
	
	// 打开摄像头设备,以读写的方式打开,返回文件描述符
	/*
		O_RDONLY:只读模式
		O_WRONLY:只写模式
		O_RDWR:读写模式
	*/
    fd = open(VIDEO_DEVICE, O_RDWR);
    if (fd == -1) 
    {
        perror("Unable to open device");
        return -1;
    }
	// 查询摄像头能力,将查询的结果存储到cap中
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) 
    {
        perror("Unable to query device");
        close(fd);
        return -1;
    }

    // 设置图像格式
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;  // 设置图像宽度
    fmt.fmt.pix.height = 480; // 设置图像高度
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; // 设置图像格式为MJPEG
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) 
    {
        perror("Unable to set format");
        close(fd);
        return -1;
    }
    // ...
}

上面使用了VIDIOC_QUERYCAP和VIDIOC_S_FMT,分别执行了查询摄像头的能力和设置图像格式的操作。这里的系统命令ioctl()在操作V4L2设备时,后续会调用video_ioctl2()这个V4L2的函数执行具体任务。

2.ioctl在V4L2的调用流程

2.1 ioctl的入口(video_ioctl2)

查找函数名所在的位置,可以使用grep命令,例如video_ioctl2()

grep -r "video_ioctl2"

应用程序通过ioctl()调用V4L2设备的IOCTL命令时,会调用到video_ioctl2()。video_ioctl2()的定义位于drivers/media/v4l2-core/v4l2-ioctl.c中,如下所示

long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{
	return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
EXPORT_SYMBOL(video_ioctl2); // 输出符号,其他模块也可以使用这个函数

video_usercopy()的作用是,检查输入命令参数,随后执行输入的func,这里执行的是__video_do_ioctl()

long
video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg,
	       v4l2_kioctl func)
{
	char	sbuf[128];
	void    *mbuf = NULL, *array_buf = NULL;
	void	*parg = (void *)arg;
	long	err  = -EINVAL;
	bool	has_array_args;
	bool	always_copy = false;
	size_t  array_size = 0;
	void __user *user_ptr = NULL;
	void	**kernel_ptr = NULL;
	unsigned int cmd = video_translate_cmd(orig_cmd);
	const size_t ioc_size = _IOC_SIZE(cmd);

	/*  Copy arguments into temp kernel buffer  */
	if (_IOC_DIR(cmd) != _IOC_NONE) {
		if (ioc_size <= sizeof(sbuf)) {
			parg = sbuf;
		} else {
			/* too big to allocate from stack */
			mbuf = kmalloc(ioc_size, GFP_KERNEL);
			if (NULL == mbuf)
				return -ENOMEM;
			parg = mbuf;
		}

		err = video_get_user((void __user *)arg, parg, cmd,
				     orig_cmd, &always_copy);
		if (err)
			goto out;
	}

	err = check_array_args(cmd, parg, &array_size, &user_ptr, &kernel_ptr);
	if (err < 0)
		goto out;
	has_array_args = err;

	if (has_array_args) {
		array_buf = kvmalloc(array_size, GFP_KERNEL);
		err = -ENOMEM;
		if (array_buf == NULL)
			goto out_array_args;
		if (in_compat_syscall())
			err = v4l2_compat_get_array_args(file, array_buf,
							 user_ptr, array_size,
							 orig_cmd, parg);
		else
			err = copy_from_user(array_buf, user_ptr, array_size) ?
								-EFAULT : 0;
		if (err)
			goto out;
		*kernel_ptr = array_buf;
	}

	/* Handles IOCTL */
	// 执行__video_do_ctrl()
	err = func(file, cmd, parg);
	if (err == -ENOTTY || err == -ENOIOCTLCMD) {
		err = -ENOTTY;
		goto out;
	}

	if (err == 0) {
		if (cmd == VIDIOC_DQBUF)
			trace_v4l2_dqbuf(video_devdata(file)->minor, parg);
		else if (cmd == VIDIOC_QBUF)
			trace_v4l2_qbuf(video_devdata(file)->minor, parg);
	}
	if (err < 0 && !always_copy && cmd != VIDIOC_SUBDEV_G_ROUTING)
		goto out;
		
	if (has_array_args) {
		*kernel_ptr = (void __force *)user_ptr;
		if (in_compat_syscall()) {
			int put_err;

			put_err = v4l2_compat_put_array_args(file, user_ptr,
							     array_buf,
							     array_size,
							     orig_cmd, parg);
			if (put_err)
				err = put_err;
		} else if (copy_to_user(user_ptr, array_buf, array_size)) {
			err = -EFAULT;
		}
	}
	if (video_put_user((void __user *)arg, parg, cmd, orig_cmd))
		err = -EFAULT;
out:
	kvfree(array_buf);
	kfree(mbuf);
	return err;
}

2.2 ioctl执行(__video_do_ioctl)

函数根据输入的命令(例如VIDIOC_STREAMON表示开启流),查找对应的执行函数,将输入的参数给到执行函数,从而执行具体的ioctl()操作,__video_do_ioctl()的定义位于drivers/media/v4l2-core/v4l2-ioctl.c中。

static long __video_do_ioctl(struct file *file,
		unsigned int cmd, void *arg)
{
	struct video_device *vfd = video_devdata(file);
	struct mutex *req_queue_lock = NULL;
	struct mutex *lock; /* ioctl serialization mutex */
	const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
	bool write_only = false;
	struct v4l2_ioctl_info default_info;
	const struct v4l2_ioctl_info *info;
	void *fh = file->private_data;
	struct v4l2_fh *vfh = NULL;
	int dev_debug = vfd->dev_debug;
	long ret = -ENOTTY;

	if (ops == NULL) {
		pr_warn("%s: has no ioctl_ops.\n",
				video_device_node_name(vfd));
		return ret;
	}

	if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags))
		vfh = file->private_data;

	/*
	 * We need to serialize streamon/off with queueing new requests.
	 * These ioctls may trigger the cancellation of a streaming
	 * operation, and that should not be mixed with queueing a new
	 * request at the same time.
	 */
	if (v4l2_device_supports_requests(vfd->v4l2_dev) &&
	    (cmd == VIDIOC_STREAMON || cmd == VIDIOC_STREAMOFF)) {
		req_queue_lock = &vfd->v4l2_dev->mdev->req_queue_mutex;

		if (mutex_lock_interruptible(req_queue_lock))
			return -ERESTARTSYS;
	}

	lock = v4l2_ioctl_get_lock(vfd, vfh, cmd, arg);

	if (lock && mutex_lock_interruptible(lock)) {
		if (req_queue_lock)
			mutex_unlock(req_queue_lock);
		return -ERESTARTSYS;
	}

	if (!video_is_registered(vfd)) {
		ret = -ENODEV;
		goto unlock;
	}

	if (v4l2_is_known_ioctl(cmd)) {
		info = &v4l2_ioctls[_IOC_NR(cmd)];

		if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
		    !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
			goto done;

		if (vfh && (info->flags & INFO_FL_PRIO)) {
			ret = v4l2_prio_check(vfd->prio, vfh->prio);
			if (ret)
				goto done;
		}
	} else {
		default_info.ioctl = cmd;
		default_info.flags = 0;
		default_info.debug = v4l_print_default;
		info = &default_info;
	}

	write_only = _IOC_DIR(cmd) == _IOC_WRITE;
	if (info != &default_info) {
		// 执行输入命令对应的函数
		ret = info->func(ops, file, fh, arg);
	} else if (!ops->vidioc_default) {
		ret = -ENOTTY;
	} else {
		ret = ops->vidioc_default(file, fh,
			vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
			cmd, arg);
	}
	// ...
}

上面使用v4l2_ioctls[_IOC_NR(cmd)],从cmd中解析出命令,随后调用这个命令对应的执行函数,v4l2_ioctls结构体的定义及命令对应的操作为

static const struct v4l2_ioctl_info v4l2_ioctls[] = {
	/*
	 * (1) 设备能力查询 
	 * VIDIOC_QUERYCAP:查询视频设备的能力,获取设备支持的功能、驱动名称、设备名称等信息。
	 * VIDIOC_G_PRIORITY:获取当前设备的优先级。
	 * VIDIOC_S_PRIORITY:设置设备的优先级。
	 * (2) 格式查询和设置
	 * VIDIOC_ENUM_FMT:枚举设备支持的像素格式。
	 * VIDIOC_G_FMT:获取当前的视频格式。
	 * VIDIOC_S_FMT:设置视频格式,如分辨率、像素格式等。
	 * VIDIOC_TRY_FMT:尝试设置视频格式,但不实际应用,用于检查格式是否有效。
	 * (3) 缓冲区管理
	 * VIDIOC_REQBUFS:请求缓冲区,为视频流分配内存缓冲区。
	 * VIDIOC_QUERYBUF:查询缓冲区的状态和信息。
	 * VIDIOC_CREATE_BUFS:创建缓冲区,类似于 VIDIOC_REQBUFS,但提供了更多选项。
	 * VIDIOC_PREPARE_BUF:准备缓冲区,用于流式传输。
	 * (4) 视频流控制
	 * VIDIOC_QBUF:将缓冲区入队到驱动程序中,准备接收或发送数据。
	 * VIDIOC_EXPBUF:导出缓冲区为文件描述符,以便与其他进程共享。
	 * VIDIOC_DQBUF:从驱动程序中出队缓冲区,获取已填充或已发送的数据。
	 * VIDIOC_STREAMON:启动视频流传输。
	 * VIDIOC_STREAMOFF:停止视频流传输.
	 * (5) 控制参数查询和设置
	 * VIDIOC_G_CTRL:获取控制参数。
	 * VIDIOC_S_CTRL:设置控制参数。
	 * VIDIOC_QUERYCTRL:查询控制参数的元信息,如名称、类型、范围等。
	 * VIDIOC_QUERYMENU:查询控制参数的菜单选项。
	 * VIDIOC_G_EXT_CTRLS:获取扩展控制参数的当前值。
	 * VIDIOC_S_EXT_CTRLS:设置扩展控制参数的值。
	 * VIDIOC_TRY_EXT_CTRLS:尝试设置扩展控制参数的值,但不实际应用。
	 * (6) 音频和调谐器设置
	 * VIDIOC_G_AUDIO:获取音频输入的当前设置。
	 * VIDIOC_S_AUDIO:设置音频输入。
	 * VIDIOC_G_TUNER:获取调谐器的当前设置。
	 * VIDIOC_S_TUNER:设置调谐器。
	 * VIDIOC_G_FREQUENCY:获取当前频率。
	 * VIDIOC_S_FREQUENCY:设置频率。
	 * VIDIOC_G_MODULATOR:获取调制器的当前设置。
	 * VIDIOC_S_MODULATOR:设置调制器。
	 * (7) 视频捕获和输出设置
	 * VIDIOC_G_FBUF:获取帧缓冲区的设置。
	 * VIDIOC_S_FBUF:设置帧缓冲区。
	 * VIDIOC_OVERLAY:控制视频叠加功能的开启或关闭.
	 * VIDIOC_G_OUTPUT:获取当前视频输出。
	 * VIDIOC_S_OUTPUT:设置视频输出。
	 * VIDIOC_ENUMOUTPUT:枚举视频输出。
	 * (8) VBI(垂直消隐间隔)设置
	 * VIDIOC_G_Sliced_VBI_CAP:获取切片 VBI 的能力。
	 * VIDIOC_G_SLICED_VBI_CAP:获取切片 VBI 的能力。
	 * (9) JPEG 压缩设置
	 * VIDIOC_G_JPEGCOMP:获取 JPEG 压缩参数。
	 * VIDIOC_S_JPEGCOMP:设置 JPEG 压缩参数。
	 * (10) 标准查询和设置
	 * VIDIOC_G_STD:获取当前电视标准。
	 * VIDIOC_S_STD:设置电视标准。
	 * VIDIOC_QUERYSTD:查询电视标准。
	 * VIDIOC_ENUMSTD:枚举电视标准。
	 * (11) 输入和输出查询和设置
	 * VIDIOC_G_INPUT:获取当前输入。
	 * VIDIOC_S_INPUT:设置输入。
	 * VIDIOC_ENUMINPUT:枚举输入。
	 * VIDIOC_G_OUTPUT:获取当前输出。
	 * VIDIOC_S_OUTPUT:设置输出。
	 * VIDIOC_ENUMOUTPUT:枚举输出。
	 * (12) 事件管理
	 * VIDIOC_DQEVENT:出队事件,获取设备生成的事件。
	 * VIDIOC_SUBSCRIBE_EVENT:订阅设备事件。
	 * VIDIOC_UNSUBSCRIBE_EVENT:取消订阅设备事件。
	 * (13) 调试和高级功能
	 * VIDIOC_LOG_STATUS:记录设备状态信息。
	 * VIDIOC_DBG_S_REGISTER:设置调试寄存器。
	 * VIDIOC_DBG_G_REGISTER:获取调试寄存器。
	 * VIDIOC_DBG_G_CHIP_INFO:获取芯片信息。
	 * (14) 其他
	 * VIDIOC_G_ENC_INDEX:获取编码索引。
	 * VIDIOC_ENCODER_CMD:发送编码器命令。
	 * VIDIOC_TRY_ENCODER_CMD:尝试发送编码器命令。
	 * VIDIOC_DECODER_CMD:发送解码器命令。
	 * VIDIOC_TRY_DECODER_CMD:尝试发送解码器命令.
	 * VIDIOC_G_DV_TIMINGS:获取数字视频时序。
	 * VIDIOC_S_DV_TIMINGS:设置数字视频时序。
	 * VIDIOC_ENUM_DV_TIMINGS:枚举数字视频时序。
	 * VIDIOC_QUERY_DV_TIMINGS:查询数字视频时序。
	 * VIDIOC_DV_TIMINGS_CAP:获取数字视频时序能力。
	 * VIDIOC_ENUM_FREQ_BANDS:枚举频率带。
	 */
	// VIDIOC_QUERYCAP是命令,v4l_querycap是回调函数
	// v4l_print_querycap是打印相关信息函数,0是VIDIOC_QUERYCAP命令关联修饰符,这里没有添加任何修饰符
	IOCTL_INFO(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
	IOCTL_INFO(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, 0),
	IOCTL_INFO(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
	IOCTL_INFO(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
	IOCTL_INFO(VIDIOC_G_FBUF, v4l_stub_g_fbuf, v4l_print_framebuffer, 0),
	IOCTL_INFO(VIDIOC_S_FBUF, v4l_stub_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_EXPBUF, v4l_stub_expbuf, v4l_print_exportbuffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_exportbuffer, flags)),
	IOCTL_INFO(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)),
	IOCTL_INFO(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_STD, v4l_stub_g_std, v4l_print_std, 0),
	IOCTL_INFO(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)),
	IOCTL_INFO(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)),
	IOCTL_INFO(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),
	IOCTL_INFO(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),
	IOCTL_INFO(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)),
	IOCTL_INFO(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_AUDIO, v4l_stub_g_audio, v4l_print_audio, 0),
	IOCTL_INFO(VIDIOC_S_AUDIO, v4l_stub_s_audio, v4l_print_audio, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_QUERYCTRL, v4l_queryctrl, v4l_print_queryctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_queryctrl, id)),
	IOCTL_INFO(VIDIOC_QUERYMENU, v4l_querymenu, v4l_print_querymenu, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_querymenu, index)),
	IOCTL_INFO(VIDIOC_G_INPUT, v4l_g_input, v4l_print_u32, 0),
	IOCTL_INFO(VIDIOC_S_INPUT, v4l_s_input, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_EDID, v4l_stub_g_edid, v4l_print_edid, INFO_FL_ALWAYS_COPY),
	IOCTL_INFO(VIDIOC_S_EDID, v4l_stub_s_edid, v4l_print_edid, INFO_FL_PRIO | INFO_FL_ALWAYS_COPY),
	IOCTL_INFO(VIDIOC_G_OUTPUT, v4l_g_output, v4l_print_u32, 0),
	IOCTL_INFO(VIDIOC_S_OUTPUT, v4l_s_output, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_ENUMOUTPUT, v4l_enumoutput, v4l_print_enumoutput, INFO_FL_CLEAR(v4l2_output, index)),
	IOCTL_INFO(VIDIOC_G_AUDOUT, v4l_stub_g_audout, v4l_print_audioout, 0),
	IOCTL_INFO(VIDIOC_S_AUDOUT, v4l_stub_s_audout, v4l_print_audioout, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_MODULATOR, v4l_g_modulator, v4l_print_modulator, INFO_FL_CLEAR(v4l2_modulator, index)),
	IOCTL_INFO(VIDIOC_S_MODULATOR, v4l_s_modulator, v4l_print_modulator, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_FREQUENCY, v4l_g_frequency, v4l_print_frequency, INFO_FL_CLEAR(v4l2_frequency, tuner)),
	IOCTL_INFO(VIDIOC_S_FREQUENCY, v4l_s_frequency, v4l_print_frequency, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_CROPCAP, v4l_cropcap, v4l_print_cropcap, INFO_FL_CLEAR(v4l2_cropcap, type)),
	IOCTL_INFO(VIDIOC_G_CROP, v4l_g_crop, v4l_print_crop, INFO_FL_CLEAR(v4l2_crop, type)),
	IOCTL_INFO(VIDIOC_S_CROP, v4l_s_crop, v4l_print_crop, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_SELECTION, v4l_g_selection, v4l_print_selection, INFO_FL_CLEAR(v4l2_selection, r)),
	IOCTL_INFO(VIDIOC_S_SELECTION, v4l_s_selection, v4l_print_selection, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_selection, r)),
	IOCTL_INFO(VIDIOC_G_JPEGCOMP, v4l_stub_g_jpegcomp, v4l_print_jpegcompression, 0),
	IOCTL_INFO(VIDIOC_S_JPEGCOMP, v4l_stub_s_jpegcomp, v4l_print_jpegcompression, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_QUERYSTD, v4l_querystd, v4l_print_std, 0),
	IOCTL_INFO(VIDIOC_TRY_FMT, v4l_try_fmt, v4l_print_format, 0),
	IOCTL_INFO(VIDIOC_ENUMAUDIO, v4l_stub_enumaudio, v4l_print_audio, INFO_FL_CLEAR(v4l2_audio, index)),
	IOCTL_INFO(VIDIOC_ENUMAUDOUT, v4l_stub_enumaudout, v4l_print_audioout, INFO_FL_CLEAR(v4l2_audioout, index)),
	IOCTL_INFO(VIDIOC_G_PRIORITY, v4l_g_priority, v4l_print_u32, 0),
	IOCTL_INFO(VIDIOC_S_PRIORITY, v4l_s_priority, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_G_SLICED_VBI_CAP, v4l_g_sliced_vbi_cap, v4l_print_sliced_vbi_cap, INFO_FL_CLEAR(v4l2_sliced_vbi_cap, type)),
	IOCTL_INFO(VIDIOC_LOG_STATUS, v4l_log_status, v4l_print_newline, 0),
	IOCTL_INFO(VIDIOC_G_EXT_CTRLS, v4l_g_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
	IOCTL_INFO(VIDIOC_S_EXT_CTRLS, v4l_s_ext_ctrls, v4l_print_ext_controls, INFO_FL_PRIO | INFO_FL_CTRL),
	IOCTL_INFO(VIDIOC_TRY_EXT_CTRLS, v4l_try_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
	IOCTL_INFO(VIDIOC_ENUM_FRAMESIZES, v4l_stub_enum_framesizes, v4l_print_frmsizeenum, INFO_FL_CLEAR(v4l2_frmsizeenum, pixel_format)),
	IOCTL_INFO(VIDIOC_ENUM_FRAMEINTERVALS, v4l_stub_enum_frameintervals, v4l_print_frmivalenum, INFO_FL_CLEAR(v4l2_frmivalenum, height)),
	IOCTL_INFO(VIDIOC_G_ENC_INDEX, v4l_stub_g_enc_index, v4l_print_enc_idx, 0),
	IOCTL_INFO(VIDIOC_ENCODER_CMD, v4l_stub_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
	IOCTL_INFO(VIDIOC_TRY_ENCODER_CMD, v4l_stub_try_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
	IOCTL_INFO(VIDIOC_DECODER_CMD, v4l_stub_decoder_cmd, v4l_print_decoder_cmd, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_TRY_DECODER_CMD, v4l_stub_try_decoder_cmd, v4l_print_decoder_cmd, 0),
	IOCTL_INFO(VIDIOC_DBG_S_REGISTER, v4l_dbg_s_register, v4l_print_dbg_register, 0),
	IOCTL_INFO(VIDIOC_DBG_G_REGISTER, v4l_dbg_g_register, v4l_print_dbg_register, 0),
	IOCTL_INFO(VIDIOC_S_HW_FREQ_SEEK, v4l_s_hw_freq_seek, v4l_print_hw_freq_seek, INFO_FL_PRIO),
	IOCTL_INFO(VIDIOC_S_DV_TIMINGS, v4l_stub_s_dv_timings, v4l_print_dv_timings, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_dv_timings, bt.flags)),
	IOCTL_INFO(VIDIOC_G_DV_TIMINGS, v4l_stub_g_dv_timings, v4l_print_dv_timings, 0),
	IOCTL_INFO(VIDIOC_DQEVENT, v4l_dqevent, v4l_print_event, 0),
	IOCTL_INFO(VIDIOC_SUBSCRIBE_EVENT, v4l_subscribe_event, v4l_print_event_subscription, 0),
	IOCTL_INFO(VIDIOC_UNSUBSCRIBE_EVENT, v4l_unsubscribe_event, v4l_print_event_subscription, 0),
	IOCTL_INFO(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_PREPARE_BUF, v4l_prepare_buf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO(VIDIOC_ENUM_DV_TIMINGS, v4l_stub_enum_dv_timings, v4l_print_enum_dv_timings, INFO_FL_CLEAR(v4l2_enum_dv_timings, pad)),
	IOCTL_INFO(VIDIOC_QUERY_DV_TIMINGS, v4l_stub_query_dv_timings, v4l_print_dv_timings, INFO_FL_ALWAYS_COPY),
	IOCTL_INFO(VIDIOC_DV_TIMINGS_CAP, v4l_stub_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, pad)),
	IOCTL_INFO(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0),
	IOCTL_INFO(VIDIOC_DBG_G_CHIP_INFO, v4l_dbg_g_chip_info, v4l_print_dbg_chip_info, INFO_FL_CLEAR(v4l2_dbg_chip_info, match)),
	IOCTL_INFO(VIDIOC_QUERY_EXT_CTRL, v4l_query_ext_ctrl, v4l_print_query_ext_ctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_query_ext_ctrl, id)),
};

以VIDIOC_STREAMON命令为例,对应的执行函数为v4l_streamon()

2.3 流启动(v4l_streamon)

流启动函数v4l_streamon()调用了vidioc_streamon()执行流启动

static int v4l_streamon(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg)
{
	return ops->vidioc_streamon(file, fh, *(unsigned int *)arg);
}

vidioc_streamon()的调用取决于设备类型,使用grep命令可以看到两个vidioc_streamon()函数,分别是vb2_ioctl_streamon()和v4l2_m2m_ioctl_streamon()。其中,vb2表示的是使用videobuf2框架进行缓冲区管理的视频设备,这些设备用于视频捕获或输出,例摄像头、图像传感器,捕获视频数据并传输到内存中;m2m指的是内存中对视频数据进行处理和转换的设备,常用于视频编解码、缩放等。

2.3.1 摄像头流传输(vb2_ioctl_streamon)

函数的定义位于drivers/media/common/videobuf2/videobuf2-v4l2.c

int vb2_ioctl_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
	struct video_device *vdev = video_devdata(file);
	
	if (vb2_queue_is_busy(vdev->queue, file)
		return -EBUSY;
	return vb2_streamon(vdev->queue, i);
}
EXPORT_SYMBOL_GPL(vb2_ioctl_streamon); // 允许声明了GPL的模块访问这个函数

vb2_streamon()的定义为

int vb2_streamon(struct vb2_queue *q, enum vb2_buf_type type)
{
	if (vb2_fileio_is_active(q)) {
		dprintk(q, 1, "file io in progress\n");
		return -EBUSY;
	}
	return vb2_core_streamon(q, type);
}
EXPORT_SYMBOL_GPL(vb2_streamon);

其中调用了vb2_core_streamon(),这个函数会告诉驱动去开启流的传输,定义在drivers/media/common/videobuf2/videobuf2-core.c

int vb2_core_streamon(struct vb2_queue *q, unsigned int type)
{
	unsigned int q_num_bufs = vb2_get_num_buffers(q);
	int ret;

	if (type != q->type) {
		dprintk(q, 1, "invalid stream type\n");
		return -EINVAL;
	}

	if (q->streaming) {
		dprintk(q, 3, "already streaming\n");
		return 0;
	}

	if (!num_buffers) {
		dprintk(q, 1, "no buffers have been allocated\n");
		return -EINVAL;
	}

	if (q_num_buffers < q->min_buffers_needed) {
		dprintk(q, 1, "need at least %u allocated buffers\n",
				q->min_buffers_needed);
		return -EINVAL;
	}

	ret = call_qop(q, prepare_streaming, q);
	if (ret)
		return ret;
	/*
	 * Tell driver to start streaming provided sufficient buffers
	 * are available.
	 */
	// 缓冲区数量大于最小值,可以开始进行视频流传输
	if (q->queued_count >= q->min_queued_buffers) {
		// 开始进行流传输
		ret = vb2_start_streaming(q);
		if (ret)
			goto unprepare;
	}

	q->streaming = 1;

	dprintk(q, 3, "successful\n");
	return 0;

unprepare:
	call_void_qop(q, unprepare_streaming, q);
	return ret;
}
EXPORT_SYMBOL_GPL(vb2_core_streamon);

vb2_start_streaming()的定义如下,这里会使用start_streaming回调函数,根据具体的驱动去调用对应的流开启函数

/*
 * vb2_start_streaming() - Attempt to start streaming.
 * @q:		videobuf2 queue
 *
 * Attempt to start streaming. When this function is called there must be
 * at least q->min_buffers_needed buffers queued up (i.e. the minimum
 * number of buffers required for the DMA engine to function). If the
 * @start_streaming op fails it is supposed to return all the driver-owned
 * buffers back to vb2 in state QUEUED. Check if that happened and if
 * not warn and reclaim them forcefully.
 */
/*
 * 尝试启动流传输。当调用此函数时,必须至少有q->min_buffers_needed个缓冲区已排队
 * (即DMA引擎正常工作所需的最小缓冲区数量)。如果@start_streaming操作失败
 * 它应该将所有驱动程序拥有的缓冲区以QUEUED状态返回给vb2。检查是否发生了这种情况
 * 如果没有,则发出警告并强制回收它们
 */
static int vb2_start_streaming(struct vb2_queue *q)
{
	struct vb2_buffer *vb;
	int ret;

	/*
	 * If any buffers were queued before streamon,
	 * we can now pass them to driver for processing.
	 */
	// 遍历缓冲区队列,将他们逐一加入到驱动程序的处理队列中
	list_for_each_entry(vb, &q->queued_list, queued_entry)
		__enqueue_in_driver(vb);

	/* Tell the driver to start streaming */
	q->start_streaming_called = 1;
	// start_streaming开始进行流传输,具体执行函数取决于使用何种驱动
	ret = call_qop(q, start_streaming, q,
		       atomic_read(&q->owned_by_drv_count));
	if (!ret)
		return 0;

	q->start_streaming_called = 0;

	dprintk(q, 1, "driver refused to start streaming\n");
	/*
	 * If you see this warning, then the driver isn't cleaning up properly
	 * after a failed start_streaming(). See the start_streaming()
	 * documentation in videobuf2-core.h for more information how buffers
	 * should be returned to vb2 in start_streaming().
	 */
	if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
		unsigned i;

		/*
		 * Forcefully reclaim buffers if the driver did not
		 * correctly return them to vb2.
		 */
		for (i = 0; i < q->num_buffers; ++i) {
			vb = q->bufs[i];
			if (vb->state == VB2_BUF_STATE_ACTIVE)
				vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);
		}
		/* Must be zero now */
		WARN_ON(atomic_read(&q->owned_by_drv_count));
	}
	/*
	 * If done_list is not empty, then start_streaming() didn't call
	 * vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED) but STATE_ERROR or
	 * STATE_DONE.
	 */
	WARN_ON(!list_empty(&q->done_list));
	return ret;
}

以UVC为例,vb2_start_streaming()会调用uvc_start_streaming()执行流启动

2.3.1.1 UVC流传输(uvc_start_streaming)

UVC全称是USB Video Class,UVC驱动能够启用摄像头的视频流传输,包括配置摄像头参数、初始化数据传输等,是USB摄像头常见的驱动,对于一些没有定制驱动的设备,通常会使用UVC驱动。如果是定制的设备,例如MTK、AML和RK等,它们会有特定的驱动程序,对于这些设备,也可以在v4l2中找到对应的驱动函数。uvc_start_streaming()的定义位于drivers/media/usb/uvc/uvc_queue.c中,这里开始进入uvc层

// uvc对应的操作函数
static const struct vb2_ops uvc_queue_qops = {
	.queue_setup = uvc_queue_setup,
	.buf_prepare = uvc_buffer_prepare,
	.buf_queue = uvc_buffer_queue,
	.buf_finish = uvc_buffer_finish,
	.wait_prepare = vb2_ops_wait_prepare,
	.wait_finish = vb2_ops_wait_finish,
	.start_streaming = uvc_start_streaming, // 启动流传输
	.stop_streaming = uvc_stop_streaming,
};

static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count)
{
	struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
	struct uvc_streaming *stream = uvc_queue_to_stream(queue);
	int ret;

	lockdep_assert_irqs_enabled();

	queue->buf_used = 0;
	// 执行uvc视频流启动任务
	ret = uvc_video_start_streaming(stream);
	if (ret == 0)
		return 0;

	spin_lock_irq(&queue->irqlock);
	uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED);
	spin_unlock_irq(&queue->irqlock);

	return ret;
}

uvc_video_start_streaming()的定义位于drivers/media/usb/uvc/uvc_video.c

int uvc_video_start_streaming(struct uvc_streaming* stream)
{
	int ret;
    // 1.时钟初始化
	ret = uvc_video_clock_init(stream);
	if (ret < 0)
		return ret;
	
	/* Commit the streaming parameters. */
	// 2.提交参数到视频流设备上
	ret = uvc_commit_video(stream, &stream->ctrl);
	if (ret < 0)
		goto error_commit;
	// 3.开始视频流传输
	ret = uvc_video_start_transfer(stream, GFP_KERNEL);
	if (ret < 0)
		goto error_video;
	return 0;

error_video:
	usb_set_interface(stream->dev->udev stream->intfnum, 0);
error_commit:
	uvc_video_clock_cleanup(stream);

	return ret;
}
2.3.1.1.1 UVC时钟初始化(uvc_video_clock_init)
static int uvc_video_clock_init(struct uvc_streaming* stream)
{
	struct uvc_clock* clock = &stream->clock;
	// 自旋锁初始化
	spin_lock_init(&clock->lock);
	clock->size = 32;

	clock->samples = kmalloc_array(clock->size, sizeof(*clock->samples), GFP_KERNEL);
	if (clock->samples == NULL)
		return -ENOMEM;
	/*
		stream->clock->head = 0;
		stream->clock->count = 0;
		stream->clock->last_sof = -1;
		stream->clock->sof_offset = -1;
	*/
	uvc_video_clock_reset(stream);
	return 0;
}
2.3.1.1.2 UVC参数提交(uvc_commit_video)

函数的作用是将前面获取到的参数提交给UVC设备

static int uvc_commit_video(struct uvc_streaming *stream,
							struct uvc_streaming_control *probe)
{
	return uvc_set_video_ctrl(stream, probe, 0);
}

uvc_set_video_ctrl()的定义如下

static int uvc_set_video_ctrl(struct uvc_streaming *stream,
		struct uvc_streaming_control *ctrl, int probe)
{
	u16 size = uvc_video_ctrl_size(stream);
	u8 *data;
	int ret;
	// GFP_KERNEL表示在内存分配过程中可以进行睡眠等待
	// 如果当前内存不足,等待足够的时候再分配
	data = kzalloc(size, GFP_KERNEL);
	if (data == NULL)
		return -ENOMEM;
	// bmHint表示掩码,指示了ctrl命令中哪些字段有效
	*(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
	data[2] = ctrl->bFormatIndex;
	data[3] = ctrl->bFrameIndex;
	*(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
	*(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
	*(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
	*(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
	*(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
	*(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
	put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
	put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);
	
	if (size >= 34) {
		put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
		data[30] = ctrl->bmFramingInfo;
		data[31] = ctrl->bPreferedVersion;
		data[32] = ctrl->bMinVersion;
		data[33] = ctrl->bMaxVersion;
	}
	
	// 发送控制命令
	ret = __uvc_query_ctrl(stream->dev, UVC_SET_CUR, 0, stream->intfnum,
		probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
		size, uvc_timeout_param);
	if (ret != size) {
		dev_err(&stream->intf->dev,
			"Failed to set UVC %s control : %d (exp. %u).\n",
			probe ? "probe" : "commit", ret, size);
		ret = -EIO;
	}

	kfree(data);
	return ret;
}

__uvc_query_ctrl的定义

static int __uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, 
						u8 intfnum, u8 cs, void *data, u16 size,
						int timeout)
{
	u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
	unsigned int pipe;
	/*
		usb_sndctrlpipe():创建一个USB控制传输的发送管道
		usb_recvctrlpipe():创建一个USB控制传输的接受管道
		
		#define usb_sndctrlpipe(dev, endpoint) \
				((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
		#define usb_rcvctrlpipe(dev, endpoint) \
				((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)

		static inline unsigned int __create_pipe(struct usb_device *dev,
						unsigned int endpoint)
		{
			return (dev->devnum << 8) | (endpoint << 15 );
		}
	*/ 
	// 根据query类型,确定创建管道的类型,endpoint为0表示是控制端点
	pipe = (query & 0x80) ? usb_rcvctrlpipe(dev->udev, 0) : usb_sndctrlpipe(dev->udev, 0);
	type |= (query & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
	// usb层传输命令消息
	return usb_control_msg(dev->udev, pipe, query, type, cs << 8, unit << 8 | intfnum, data, size, timeout);
}

usb_control_msg()的作用是在usb层发送控制传输消息,定义在drivers/usb/core/message.c中,从这里开始就到了usb层级

int usb_control_msg(struct usb_device *dev, unsigned int pipeline, __u8 request,
					__u8 requesttype, __u16 value, __u16 index, void *data,
					__u16 size, int timeout)
{
	struct usb_ctrlrequest *dir;
	int ret;
	/* GFP_NOIO表示在内存分配过程中,不允许进行IO的操作,
	   如果此时内存不足,内核不会尝试写入交换空间或者刷新文件系统缓存来释放内存
	 */ 
	dr = kmalloc(sizeof(struct usb_ctrlrequest, GFP_NOIO);
	if (!dir)
		return -ENOMEM;
	
	dr->bRequestType = requesttype;
	dr->bRequest = request;
	dr->wValue = cpu_to_le16(value);
	dr->wIndex = cpu_to_le16(index);
	dr->wLength = cpu_to_le16(size);

	ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout);

	/* Linger a bit, prior to the next control message */
	if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG)
		msleep(200);
	
	kfree(dr);
	return ret;
}
EXPORT_SYMBOL_GPL(usb_control_msg);

usb_internal_control_msg()的定义如下

/* returns status (negative) or length (positive) */
static int usb_internal_control_msg(struct usb_device *usb_dev, 
									unsigned int pipe,
									struct usb_ctrlrequest *cmd,
									void *data, int len, int timeout)
{
	struct urb *urb; // USB Request Block
	int retv;
	int length;

	urb = usb_alloc_urb(0, GFP_NOIO);
	if (!usb)
		return -ENOMEM;
	// 设置URB的各个字段,以便进行USB控制传输操作
	usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char*)cmd, data,
						 len, usb_api_blocking_completion, NULL);
	// 传输msg
	retv = usb_start_wait_urb(urb, timeout, &length);
	if (retv < 0)
		return retv;
	else
		return length;
}

usb_start_wait_urb()的定义如下

static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length)
{
	struct api_context ctx;
	unsigned long expire;
	int retval;
	// 初始化完成变量
	init_completion(&ctx.done);
	urb->context = &ctx;
	urb->actual_length = 0;
	/* 提交urb到usb核心,以便usb核心可以执行urb中定义的usb数据传输操作
	 * HCD: Host Controller Driver,主机控制器
	 */ 
	retval = usb_submit_urb(urb, GFP_NOIO);
	if (unlikely(retval))
		goto out;
		
	expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;
	if (!wait_for_completion_timeout(&ctx.done, expire)) {
		usb_kill_urb(urb);
		retval = (ctx.status == -ENOENT ? -ETIMEDOUT : ctx.status);

		dev_dbg(&urb->dev->dev, 
				"%s timed out on ep%d%s len=%u/%u\n",
				current->comm,
				usb_endpoint_num(&urb->ep->desc),
				usb_urb_dir_in(urb) ? "in" : "out",
				urb->actual_length,
				urb->transfer_buffer_length);
	} else
		retval = ctx.status;
out:
	if (actual_length)
		*actual_length = urb->actual_length;
	
	usb_free_urb(urb);
	return retval;
}

usb_submit_urb()定义在drivers/usb/core/urb.c,其中会调用usb_hcd_submit_urb()进行urb传输,usb_hcd_submit_urb()的定义位于drivers/usb/core/hcd.c中

int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
	int status;
	// 获取hcd
	struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus);
	// 增加urb引用计数
	usb_get_urb(urb);
	// 增加urb使用计数
	atomic_inc(&urb->use_count);
	// 增加urb编号,用于跟踪
	atomic_inc(&urb->urbnum);
	// usbmon记录urb提交信息,以便进行usb监控和调试
	usbmon_urb_submit(&hcd->self, urb);

	if (is_root_hub(urb->dev)) {
		// 加入root hub队列
		status = rh_urb_enqueue(hcd, urb);
	} else {
		status = map_urb_for_dma(hcd, urb, mem_flags);
		if (likely(status == 0)) {
			/* 
			 * 将urb添加到驱动程序的队列中,这里可能是ehci、xhci等等驱动程序,
			 * 在底层驱动级别,调用urb_enqueue()函数,从而将urb传递给硬件设备,
			 * 例如当前是streamon的指令,会传递摄像头采集size、framerate等信息
			 */ 
			status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
			if (unlikely(status))
				unmap_urb_for_dma(hcd, urb);
		}
	}
	// 处理错误情况
	if (unlikely(status)) {
		usbmon_urb_submit_error(&hcd->self, urb, status);
		urb->hcpriv = NULL;
		INIT_LIST_HEAD(&urb->urb_list);
		atomic_dec(&urb->use_count);

		/*
		 * Order the write of urb->use_count above before the read 
		 * of urb->reject below. Pairs with the memory barriers in
		 * usb_kill_urb() and usb_poison_urb(). 
		 */
		 smp_mb_after_atomic();

		atomic_dec(&urb->dev->urbnum);
		if (atomic_read(&urb->reject))
			wake_up(&usb_kill_urb_queue);
		usb_put_urb(urb);
	}
	return status;
}

2.3.2 内存流传输(v4l2_m2m_ioctl_streamon)

v4l2_m2m_ioctl_streamon()的作用是启动内存到内存(memory to memory, m2m)设备的视频流,m2m设备通常用于在内存中对视频数据进行处理和转换,例如视频编解码器、缩放器或格式转换器。v4l2_m2m_ioctl_streamon()的定义位于drivers/media/v4l2-core/v4l2-mem2mem.c中。

int v4l2_m2m_ioctl_streamon(struct file *file, void *priv,
				enum v4l2_buf_type type)
{
	struct v4l2_fh *fh = file->private_data;

	return v4l2_m2m_streamon(file, fh->m2m_ctx, type);
}
EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_streamon);

v4l2_m2m_streamon()的定义如下,其中也会调用vb2_streamon()这个函数,不过和前面摄像头采集不太一样的地方在于,start_streaming()回调函数的主体不一样,以mtk的编解码为例

int v4l2_m2m_streamon(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
		      enum v4l2_buf_type type)
{
	struct vb2_queue *vq;
	int ret;

	vq = v4l2_m2m_get_vq(m2m_ctx, type);
	ret = vb2_streamon(vq, type);
	if (!ret)
		v4l2_m2m_try_schedule(m2m_ctx);

	return ret;
}
EXPORT_SYMBOL_GPL(v4l2_m2m_streamon);

以mtk中的encoder和decoder为例,编码流启动函数vb2ops_venc_start_streaming()定义位于driver/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c中

static int vb2ops_venc_start_streaming(struct vb2_queue *q, unsigned int count)
{
	struct mtk_vcodec_enc_ctx *ctx = vb2_get_drv_priv(q);
	struct venc_enc_param param;
	int ret;
	int i;

	/* Once state turn into MTK_STATE_ABORT, we need stop_streaming
	  * to clear it
	  */
	if ((ctx->state == MTK_STATE_ABORT) || (ctx->state == MTK_STATE_FREE)) {
		ret = -EIO;
		goto err_start_stream;
	}

	/* Do the initialization when both start_streaming have been called */
	if (V4L2_TYPE_IS_OUTPUT(q->type)) { // 检查是否是输出队列
		if (!vb2_start_streaming_called(&ctx->m2m_ctx->cap_q_ctx.q))
			return 0;
	} else {
		if (!vb2_start_streaming_called(&ctx->m2m_ctx->out_q_ctx.q))
			return 0;
	}
	// 准备编码参数,从ctx中提取到param中
	mtk_venc_set_param(ctx, &param);
	// 将准备好的编码参数应用到编码器中
	ret = venc_if_set_param(ctx, VENC_SET_PARAM_ENC, &param);
	if (ret) {
		mtk_v4l2_venc_err(ctx, "venc_if_set_param failed=%d", ret);
		ctx->state = MTK_STATE_ABORT;
		goto err_start_stream;
	}
	ctx->param_change = MTK_ENCODE_PARAM_NONE;

	if ((ctx->q_data[MTK_Q_DATA_DST].fmt->fourcc == V4L2_PIX_FMT_H264) &&
	    (ctx->enc_params.seq_hdr_mode !=
				V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE)) {
		ret = venc_if_set_param(ctx,
					VENC_SET_PARAM_PREPEND_HEADER,
					NULL);
		if (ret) {
			mtk_v4l2_venc_err(ctx, "venc_if_set_param failed=%d", ret);
			ctx->state = MTK_STATE_ABORT;
			goto err_start_stream;
		}
		ctx->state = MTK_STATE_HEADER;
	}

	return 0;

err_start_stream:
	for (i = 0; i < vb2_get_num_buffers(q); ++i) {
		struct vb2_buffer *buf = vb2_get_buffer(q, i);

		/*
		 * FIXME: This check is not needed as only active buffers
		 * can be marked as done.
		 */
		if (buf && buf->state == VB2_BUF_STATE_ACTIVE) {
			mtk_v4l2_venc_dbg(0, ctx, "[%d] id=%d, type=%d, %d->VB2_BUF_STATE_QUEUED",
					  ctx->id, i, q->type, (int)buf->state);
			v4l2_m2m_buf_done(to_vb2_v4l2_buffer(buf),
					  VB2_BUF_STATE_QUEUED);
		}
	}

	return ret;
}
mtk平台内存h264编码
1.填充数据到缓冲区(v4l_qbuf)

在上述的代码之中,只设置了编码参数,没有执行编码过程。编码过程需要先进行缓冲区入队,即VIDIOC_QBUF命令给出

ioctl(encoder, VIDIOC_QBUF, &buf); // encoder是编码器,buf是数据缓冲区

VIDIOC_QBUFv4l_qbuf的定义位于drivers/media/v4l2-core/v4l2-ioctl.c中

static int v4l_qbuf(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_buffer *p = arg;
	int ret = check_fmt(file, p->type);

	return ret ? ret : ops->vidioc_qbuf(file, fh, p);
}

对于编码而言,是内存到内存的处理流程,所以vidioc_qbuf()的使用是v4l2_m2m_ioctl_qbuf(),定义在drivers/media/v4l2-core/v4l2-mem2mem.c中

int v4l2_m2m_ioctl_qbuf(struct file *file, void *priv,
				struct v4l2_buffer *buf)
{
	struct v4l2_fh *fh = file->private_data;

	return v4l2_m2m_qbuf(file, fh->m2m_ctx, buf);
}
EXPORT_SYMBOL_GPL(v4l2_m2m_ioctl_qbuf);

v4l2_m2m_qbuf()的定义

int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx,
		  struct v4l2_buffer *buf)
{
	struct video_device *vdev = video_devdata(file);
	struct vb2_queue *vq;
	int ret;

	vq = v4l2_m2m_get_vq(m2m_ctx, buf->type);
	if (V4L2_TYPE_IS_CAPTURE(vq->type) &&
	    (buf->flags & V4L2_BUF_FLAG_REQUEST_FD)) {
		dprintk("%s: requests cannot be used with capture buffers\n",
			__func__);
		return -EPERM;
	}

	ret = vb2_qbuf(vq, vdev->v4l2_dev->mdev, buf);
	if (ret)
		return ret;

	// ...
	return 0;
}
EXPORT_SYMBOL_GPL(v4l2_m2m_qbuf);

vb2_qbuf()的定义位于drivers/media/common/videobuf2/videobuf2-v4l2.c中

int vb2_qbuf(struct vb2_queue *q, struct media_device *mdev,
	     struct v4l2_buffer *b)
{
	struct media_request *req = NULL;
	struct vb2_buffer *vb;
	int ret;

	// ... 
	if (ret)
		return ret;
	// qbuf
	ret = vb2_core_qbuf(q, vb, b, req);
	if (req)
		media_request_put(req);
	return ret;
}
EXPORT_SYMBOL_GPL(vb2_qbuf);

vb2_core_qbuf()的定义位于drivers/media/common/videobuf2/videobuf2-core.c中

int vb2_core_qbuf(struct vb2_queue *q, struct vb2_buffer *vb, void *pb,
		  struct media_request *req)
{
	enum vb2_buffer_state orig_state;
	int ret;

	// ...
	
	if (vb->state != VB2_BUF_STATE_IN_REQUEST)
		q->uses_qbuf = 1;

	switch (vb->state) {
	case VB2_BUF_STATE_DEQUEUED:
	case VB2_BUF_STATE_IN_REQUEST:
		if (!vb->prepared) {
			ret = __buf_prepare(vb);
			if (ret)
				return ret;
		}
		break;
	case VB2_BUF_STATE_PREPARING:
		dprintk(q, 1, "buffer still being prepared\n");
		return -EINVAL;
	default:
		dprintk(q, 1, "invalid buffer state %s\n",
			vb2_state_name(vb->state));
		return -EINVAL;
	}

	/*
	 * Add to the queued buffers list, a buffer will stay on it until
	 * dequeued in dqbuf.
	 */
	orig_state = vb->state;
	list_add_tail(&vb->queued_entry, &q->queued_list);
	q->queued_count++;
	q->waiting_for_buffers = false;
	vb->state = VB2_BUF_STATE_QUEUED;

	if (pb)
		call_void_bufop(q, copy_timestamp, vb, pb);

	trace_vb2_qbuf(q, vb);

	/*
	 * If already streaming, give the buffer to driver for processing.
	 * If not, the buffer will be given to driver on next streamon.
	 */
	// 将buffer送到driver
	if (q->start_streaming_called)
		__enqueue_in_driver(vb);

	/* Fill buffer information for the userspace */
	if (pb)
		call_void_bufop(q, fill_user_buffer, vb, pb);
	// ...
	dprintk(q, 2, "qbuf of buffer %d succeeded\n", vb->index);
	return 0;
}
EXPORT_SYMBOL_GPL(vb2_core_qbuf);

__enqueue_in_driver()的定义

/*
 * __enqueue_in_driver() - enqueue a vb2_buffer in driver for processing
 */
static void __enqueue_in_driver(struct vb2_buffer *vb)
{
	struct vb2_queue *q = vb->vb2_queue;

	vb->state = VB2_BUF_STATE_ACTIVE;
	atomic_inc(&q->owned_by_drv_count);

	trace_vb2_buf_queue(q, vb);
	// 调用buf_queue函数
	call_void_vb_qop(vb, buf_queue, vb);
}

mtk平台的编码入队函数buf_queue()函数为vb2ops_venc_buf_queue(),定义在drivers/media/platform/mediatek/vcodec/encoder/mtk_vcodec_enc.c中

static void vb2ops_venc_buf_queue(struct vb2_buffer *vb)
{
	struct mtk_vcodec_enc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
	struct vb2_v4l2_buffer *vb2_v4l2 =
			container_of(vb, struct vb2_v4l2_buffer, vb2_buf);

	struct mtk_video_enc_buf *mtk_buf =
			container_of(vb2_v4l2, struct mtk_video_enc_buf,
				     m2m_buf.vb);

	if ((vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) &&
	    (ctx->param_change != MTK_ENCODE_PARAM_NONE)) {
		mtk_v4l2_venc_dbg(1, ctx, "[%d] Before id=%d encode parameter change %x",
				  ctx->id, vb2_v4l2->vb2_buf.index, ctx->param_change);
		mtk_buf->param_change = ctx->param_change;
		mtk_buf->enc_params = ctx->enc_params;
		ctx->param_change = MTK_ENCODE_PARAM_NONE;
	}
	// m2m入队函数
	v4l2_m2m_buf_queue(ctx->m2m_ctx, to_vb2_v4l2_buffer(vb));
}

其中调用了v4l2_m2m_buf_queue()函数,定义在drivers/media/v4l2-core/v4l2-mem2mem.c中,该函数就将待编码数据填充到队列之中

void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx,
		struct vb2_v4l2_buffer *vbuf)
{
	struct v4l2_m2m_buffer *b = container_of(vbuf,
				struct v4l2_m2m_buffer, vb);
	struct v4l2_m2m_queue_ctx *q_ctx;
	unsigned long flags;

	q_ctx = get_queue_ctx(m2m_ctx, vbuf->vb2_buf.vb2_queue->type);
	if (!q_ctx)
		return;

	spin_lock_irqsave(&q_ctx->rdy_spinlock, flags);
	// 添加b->list到q_ctx->rdy_queue的队列之中
	list_add_tail(&b->list, &q_ctx->rdy_queue);
	q_ctx->num_rdy++;
	spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags);
}
EXPORT_SYMBOL_GPL(v4l2_m2m_buf_queue);
2.worker线程执行编码任务(mtk_venc_worker)

对于mtk平台,具体处理缓冲区数据的函数是mtk_venc_worker()。前面已经将数据填充到缓冲区,在worker线程中会去处理这些数据。

/*
 * v4l2_m2m_streamoff() holds dev_mutex and waits mtk_venc_worker()
 * to call v4l2_m2m_job_finish().
 * If mtk_venc_worker() tries to acquire dev_mutex, it will deadlock.
 * So this function must not try to acquire dev->dev_mutex.
 * This means v4l2 ioctls and mtk_venc_worker() can run at the same time.
 * mtk_venc_worker() should be carefully implemented to avoid bugs.
 */
static void mtk_venc_worker(struct work_struct *work)
{
	struct mtk_vcodec_enc_ctx *ctx = container_of(work, struct mtk_vcodec_enc_ctx,
				    encode_work);
	struct vb2_v4l2_buffer *src_buf, *dst_buf;
	struct venc_frm_buf frm_buf;
	struct mtk_vcodec_mem bs_buf;
	struct venc_done_result enc_result;
	int ret, i;

	/* check dst_buf, dst_buf may be removed in device_run
	 * to stored encdoe header so we need check dst_buf and
	 * call job_finish here to prevent recursion
	 */
	dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);
	if (!dst_buf) {
		v4l2_m2m_job_finish(ctx->dev->m2m_dev_enc, ctx->m2m_ctx);
		return;
	}

	src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx);

	/*
	 * If we see the flush buffer, send an empty buffer with the LAST flag
	 * to the client. is_flushing will be reset at the time the buffer
	 * is dequeued.
	 */
	if (src_buf == &ctx->empty_flush_buf.vb) {
		vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0);
		dst_buf->flags |= V4L2_BUF_FLAG_LAST;
		v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
		v4l2_m2m_job_finish(ctx->dev->m2m_dev_enc, ctx->m2m_ctx);
		return;
	}

	memset(&frm_buf, 0, sizeof(frm_buf));
	for (i = 0; i < src_buf->vb2_buf.num_planes ; i++) {
		frm_buf.fb_addr[i].dma_addr =
				vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, i);
		frm_buf.fb_addr[i].size =
				(size_t)src_buf->vb2_buf.planes[i].length;
	}
	bs_buf.va = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
	bs_buf.dma_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
	bs_buf.size = (size_t)dst_buf->vb2_buf.planes[0].length;

	mtk_v4l2_venc_dbg(2, ctx,
			  "Framebuf PA=%llx Size=0x%zx;PA=0x%llx Size=0x%zx;PA=0x%llx Size=%zu",
			  (u64)frm_buf.fb_addr[0].dma_addr, frm_buf.fb_addr[0].size,
			  (u64)frm_buf.fb_addr[1].dma_addr, frm_buf.fb_addr[1].size,
			  (u64)frm_buf.fb_addr[2].dma_addr, frm_buf.fb_addr[2].size);
	// 编码入口
	ret = venc_if_encode(ctx, VENC_START_OPT_ENCODE_FRAME,
			     &frm_buf, &bs_buf, &enc_result);

	dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp;
	dst_buf->timecode = src_buf->timecode;

	// ...
}

venc_if_encoder()定义在drivers/media/platform/mediatek/vcodec/encoder/venc_drv_if.c中

int venc_if_encode(struct mtk_vcodec_enc_ctx *ctx,
		   enum venc_start_opt opt, struct venc_frm_buf *frm_buf,
		   struct mtk_vcodec_mem *bs_buf,
		   struct venc_done_result *result)
{
	int ret = 0;
	unsigned long flags;

	mtk_venc_lock(ctx);

	spin_lock_irqsave(&ctx->dev->irqlock, flags);
	ctx->dev->curr_ctx = ctx;
	spin_unlock_irqrestore(&ctx->dev->irqlock, flags);
	// 打开电源,确保编码器可用
	ret = mtk_vcodec_enc_pw_on(&ctx->dev->pm);
	if (ret)
		goto venc_if_encode_pw_on_err;
	mtk_vcodec_enc_clock_on(&ctx->dev->pm);
	// 调用mtk的编码接口,执行编码任务,进入mtk底层的硬件编码流程
	ret = ctx->enc_if->encode(ctx->drv_handle, opt, frm_buf,
				  bs_buf, result);
	mtk_vcodec_enc_clock_off(&ctx->dev->pm);
	mtk_vcodec_enc_pw_off(&ctx->dev->pm);

	spin_lock_irqsave(&ctx->dev->irqlock, flags);
	ctx->dev->curr_ctx = NULL;
	spin_unlock_irqrestore(&ctx->dev->irqlock, flags);

venc_if_encode_pw_on_err:
	mtk_venc_unlock(ctx);
	return ret;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值