目录
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, ¶m);
// 将准备好的编码参数应用到编码器中
ret = venc_if_set_param(ctx, VENC_SET_PARAM_ENC, ¶m);
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;
}