最近要在开源的平板上做Linux的项目,需要用到视频流服务器,首选肯定是MJPG-Streamer,但是按照之前的调试记录发现有问题,总是报一个I2C的错误,错误信息如下所示:
MJPG-streamer [81]: starting application MJPG Streamer Version.: 2.0 MJPG-streamer [81]: MJPG Streamer Version.: 2.0 i: Using V4L2 device.: /dev/video0 MJPG-streamer [81]: Using V4L2 device.: /dev/video0 i: Desired Resolution: 640 x 480 MJPG-streamer [81]: Desired Resolution: 640 x 480 i: Frames Per Second.: 5 MJPG-streamer [81]: Frames Per Second.: 5 i: Format............: YUV MJPG-streamer [81]: Format............: YUV i: JPEG Quality......: 80 MJPG-streamer [81]: JPEG Quality......: 80 [ 37.075219] [i2c2] incomplete xfer (0x20) [ 37.079274] [CSI_ERR][GC0308]sensor_write error! [ 37.087464] [CSI_ERR][GC0308]sensor_write_err! reg_num = $? value = ? [ 37.094063] [CSI]buffer_setup, buffer count=4, size=614400 Unable to map buffer: Invalid argument Init v4L2 failed !! exit fatal i: init_VideoIn failed MJPG-streamer [81]: init_VideoIn failed实际上这里面包含两个错误,最后四行报的错误很明显,就是mmap的时候参数出错了。
Unable to map buffer: Invalid argument Init v4L2 failed !! exit fatal i: init_VideoIn failed MJPG-streamer [81]: init_VideoIn failed在plugins/input_uvc/v4l2uvc.c源码的第200行static int init_v4l2(struct vdIn *vd)函数中找到mmap函数:
vd->mem[i] = mmap(0 /* start anywhere */ , //vd->buf.length, PROT_READ, MAP_SHARED, vd->fd, /* FSPAD_702 Linux added by LeeSheen */ vd->buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, vd->fd, vd->buf.m.offset);
在第96行我们找到 init_v4l2函数中open函数:
if ((vd->fd = open(vd->videodevice, O_RDWR)) == -1) { perror("ERROR opening V4L interface"); return -1; }
我们发现打开的时候是使用读写方式打开的,而mmap的时候参数是以只读的方式打开的。在man手册里描述mmap()函数有这么一段话:
The prot argument describes the desired memory protection of the mapping (and must not conflict with the open mode of the file). It is either PROT_NONE or the bitwise OR of one or more of the following flags:所以看的出我们用读写打开,mmap的时候也必须以读写的方式映射,所以mmap函数应该改成:
vd->mem[i] = mmap(0 /* start anywhere */ , /* FSPAD_702 Linux added by LeeSheen */ vd->buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, vd->fd, vd->buf.m.offset);改完之后测试,发现mmap的错误消失了,但I2C的错误仍然存在。错误信息是:
[ 37.075219] [i2c2] incomplete xfer (0x20) [ 37.079274] [CSI_ERR][GC0308]sensor_write error! [ 37.087464] [CSI_ERR][GC0308]sensor_write_err! reg_num = $? value = ?I2C跟到驱动的sensor_write函数中,位置是drivers/media/video/sun5i_csi/device/gc0308.c:
static int sensor_write(struct v4l2_subdev *sd, unsigned char *reg, unsigned char *value) { struct i2c_client *client = v4l2_get_subdevdata(sd); struct i2c_msg msg; unsigned char data[REG_STEP]; int ret,i; for(i = 0; i < REG_ADDR_STEP; i++) data[i] = reg[i]; for(i = REG_ADDR_STEP; i < REG_STEP; i++) data[i] = value[i-REG_ADDR_STEP]; msg.addr = client->addr; msg.flags = 0; msg.len = REG_STEP; msg.buf = data; //printk("addr = %d, len = %d, buf = %d, %d, %d\n", msg.addr, msg.len, msg.buf[0], msg.buf[1], msg.buf[2]); ret = i2c_transfer(client->adapter, &msg, 1); if (ret > 0) { ret = 0; } else if (ret < 0) { csi_dev_err("sensor_write error!\n"); } return ret; }发现就是I2C写函数出错了,可是安卓中摄像头是正常的,所以猜想问题还是应该出现摄像头初始化上,我看了看csi驱动,发现了这样一个函数:
static int internal_s_input(struct csi_dev *dev, unsigned int i) { struct v4l2_control ctrl; int ret; if (i > dev->dev_qty-1) { csi_err("set input error!\n"); return -EINVAL; } if (i == dev->input) return 0; csi_dbg(0,"input_num = %d\n",i); if(dev->input != -1) { /*Power down current device*/ ret = v4l2_subdev_call(dev->sd,core, s_power, CSI_SUBDEV_STBY_ON); if(ret < 0) goto altend; } /* Alternate the device info and select target device*/ ret = update_ccm_info(dev, dev->ccm_cfg[i]); if (ret < 0) { csi_err("Error when set ccm info when selecting input!,input_num = %d\n",i); goto recover; } /* change the csi setting */ csi_dbg(0,"dev->ccm_info->vref = %d\n",dev->ccm_info->vref); csi_dbg(0,"dev->ccm_info->href = %d\n",dev->ccm_info->href); csi_dbg(0,"dev->ccm_info->clock = %d\n",dev->ccm_info->clock); csi_dbg(0,"dev->ccm_info->mclk = %d\n",dev->ccm_info->mclk); dev->csi_mode.vref = dev->ccm_info->vref; dev->csi_mode.href = dev->ccm_info->href; dev->csi_mode.clock = dev->ccm_info->clock; csi_clk_out_set(dev); /* Initial target device */ ret = v4l2_subdev_call(dev->sd,core, s_power, CSI_SUBDEV_STBY_OFF); if (ret!=0) { csi_err("sensor standby off error when selecting target device!\n"); goto recover; } ret = v4l2_subdev_call(dev->sd,core, init, 0); if (ret!=0) { csi_err("sensor initial error when selecting target device!\n"); goto recover; } /* Set the initial flip */ ctrl.id = V4L2_CID_VFLIP; ctrl.value = dev->vflip; ret = v4l2_subdev_call(dev->sd,core, s_ctrl, &ctrl); if (ret!=0) { csi_err("sensor sensor_s_ctrl V4L2_CID_VFLIP error when vidioc_s_input!input_num = %d\n",i); } ctrl.id = V4L2_CID_HFLIP; ctrl.value = dev->hflip; ret = v4l2_subdev_call(dev->sd,core, s_ctrl, &ctrl); if (ret!=0) { csi_err("sensor sensor_s_ctrl V4L2_CID_HFLIP error when vidioc_s_input!input_num = %d\n",i); } dev->input = i; ret = 0; ……果然,驱动中把本应该写在open里函数初始化流程写到了vidioc_s_input函数中,那下面的步骤就简单了,要么就是改驱动,要么就是改MJPG-Streamer,在MJPG-Streamer中只需要加一个ioctl函数即可,所以我们选择修改MJPG-Streamer。
在MJPG-Streamer源码中plugins/input_uvc/v4l2uvc.c第99行,init_v4l2函数中,open函数之后,添加下面代码:
/* FSPAD_702 Linux added by LeeSheen */ #if 1 struct v4l2_input inp; inp.index = 0; if (-1 == ioctl(vd->fd, VIDIOC_S_INPUT, &inp)) printf("VIDIOC_S_INPUT error\n"); #endif再次运行,果然没有I2C传输错误的提示了。
测试一下,显示正常,说明MJPG-Streamer移植就成功了。
下一篇文章会介绍MJPG-Streamer的详细移植过程。