最近要在开源的平板上做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的详细移植过程。