Linux 下摄像头视频采集与显示
我将它设置为MJPEG格式,同样不行,所以图2最后同样出错。(那时正兴高采烈地做毕业设计,这个问题让我足足郁闷了好几天。我想不通是什么原因)
图1 摄像头信息
图2 又一个信息
下面简单讲一下程序片段,具体的程序,参见附录中。
(1)、分配内存
switch (vd_info->format) /**< format will be also ok */
{
case V4L2_PIX_FMT_MJPEG:
vd_info->tmp_buffer =
(uint8 *)calloc(1, (size_t)vd_info->frame_size_in);
if (vd_info->tmp_buffer == NULL)
error_out("unable alloc tmp_buffer");
vd_info->frame_buffer =
(uint8 *)calloc(1, (size_t)vd_info->width * (vd_info->height+8) * 2);
if (vd_info->frame_buffer == NULL)
error_out("unable alloc frame_buffer");
break;
case V4L2_PIX_FMT_YUYV:
vd_info->frame_buffer =
(uint8 *)calloc(1,(size_t)vd_info->frame_size_in);
if (vd_info->frame_buffer == NULL)
error_out("unable alloc frame_buffer");
break;
default:
msg_out("error!/n");
return -1;
break;
}
因为YUYV是一种原始数据,可以直接显示,不需要编解码,而MJPEG格式的,需要解码,所以分要分配两个缓冲区。
(2)、打开,O_NONBLOCK是以非阻塞方式打开。
vd_info->camfd = open(device, O_RDWR /*| O_NONBLOCK*/, 0);
if (vd_info->camfd < 0)
error_out("can not open the device");
(3)、查询
if (-1 == ioctl(vd_info->camfd, VIDIOC_QUERYCAP, &vd_info->cap))
error_out("query camera failed");
if (0 == (vd_info->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
debug_msg("video capture not supported./n");
return -1;
}
(4)、设置格式
memset(&vd_info->fmt, 0, sizeof(struct v4l2_format));
vd_info->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd_info->fmt.fmt.pix.width = width;
vd_info->fmt.fmt.pix.height = height;
vd_info->fmt.fmt.pix.field =V4L2_FIELD_ANY;
vd_info->fmt.fmt.pix.pixelformat = format;
if (-1 == ioctl(vd_info->camfd, VIDIOC_S_FMT, &vd_info->fmt))
error_out("unable to set format ");
(5)、查询缓冲区、映射到用户空间内存
memset(&vd_info->rb, 0, sizeof(struct v4l2_requestbuffers));
vd_info->rb.count = NB_BUFFER; /**< 4 buffers */
vd_info->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd_info->rb.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(vd_info->camfd, VIDIOC_REQBUFS, &vd_info->rb))
error_out("unable to allocte buffers");
/* map the buffers(4 buffer) */
for (i = 0; i < NB_BUFFER; i++)
{
memset(&vd_info->buf, 0, sizeof(struct v4l2_buffer));
vd_info->buf.index = i;
vd_info->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd_info->buf.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(vd_info->camfd, VIDIOC_QUERYBUF, &vd_info->buf))
error_out("unable to query buffer");
/* map it, 0 means anywhere */
vd_info->mem[i] =
mmap(0, vd_info->buf.length, PROT_READ, MAP_SHARED,
vd_info->camfd, vd_info->buf.m.offset);
/* MAP_FAILED = (void *)-1 */
if (MAP_FAILED == vd_info->mem[i])
error_out("unable to map buffer");
}
(6)、进入队列
/* queue the buffers */
for (i = 0; i < NB_BUFFER; i++)
{
memset(&vd_info->buf, 0, sizeof(struct v4l2_buffer));
vd_info->buf.index = i;
vd_info->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd_info->buf.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(vd_info->camfd, VIDIOC_QBUF, &vd_info->buf))
error_out("unable to queue the buffers");
}
(7)、开始捕获(发出捕获信号)
vd_info->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(vd_info->camfd, VIDIOC_STREAMON, &vd_info->type))
error_out("unable to start capture");
vd_info->is_streaming = 1;
debug_msg("stream on OK!!/n");
debug_msg("===============================/n/n");
(8)、采集(从缓冲区队列中取出数据,再将数据的内存复制另一内存区)
static int count = 0;
if (!vd_info->is_streaming) /**< if stream is off, start it */
{
if (v4l2_on(vd_info)) /**< failed */
goto err;
}
memset(&vd_info->buf, 0, sizeof(struct v4l2_buffer));
vd_info->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd_info->buf.memory = V4L2_MEMORY_MMAP;
/* get data from buffers */
if (-1 == ioctl(vd_info->camfd, VIDIOC_DQBUF, &vd_info->buf))
{
msg_out("unable to dequeue buffer/n");
goto err;
}
switch (vd_info->format)
{
case V4L2_PIX_FMT_MJPEG:
if (vd_info->buf.bytesused <= HEADFRAME1)
{
msg_out("ignore empty frame.../n");
return 0;
}
/* we can save tmp_buff to a jpg file,just write it! */
memcpy(vd_info->tmp_buffer, vd_info->mem[vd_info->buf.index],
vd_info->buf.bytesused);
/* here decode MJPEG,so we can dispaly it */
if (jpeg_decode(&vd_info->frame_buffer, vd_info->tmp_buffer,
&vd_info->width, &vd_info->height) < 0 )
{
msg_out("decode jpeg error/n");
goto err;
}
break;
case V4L2_PIX_FMT_YUYV:
if (vd_info->buf.bytesused > vd_info->frame_size_in)
memcpy(vd_info->frame_buffer, vd_info->mem[vd_info->buf.index],
(size_t)vd_info->frame_size_in);
else
memcpy(vd_info->frame_buffer, vd_info->mem[vd_info->buf.index],
(size_t)vd_info->buf.bytesused);
break;
default:
goto err;
break;
}
/* here you can process the frame! */
v4l2_process(vd_info);
/* queue buffer again */
if (-1 == ioctl(vd_info->camfd, VIDIOC_QBUF, &vd_info->buf))
{
fprintf(stderr,"requeue error/n");
goto err;
}
debug_msg("frame:%d/n", count++);
debug_msg("frame size in: %d KB/n", vd_info->frame_size_in>>10);
return 0;
err:
vd_info->is_quit = 0;
return -1;
(9)、停止捕获(发送停止信号)
vd_info->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(vd_info->camfd, VIDIOC_STREAMOFF, &vd_info->type))
error_out("unable to stop capture");
vd_info->is_streaming = 0;
debug_msg("stream off OK!/n");
debug_msg("===============================/n/n");
(10)、关闭设备(释放内存、关闭设备)
uint16 i = 0;
if (vd_info->is_streaming) /**< stop if it is still capturing */
v4l2_off(vd_info);
if (vd_info->frame_buffer)
free(vd_info->frame_buffer);
if (vd_info->tmp_buffer)
free(vd_info->tmp_buffer);
vd_info->frame_buffer = NULL;
/* it is a good thing to unmap! */
for (i = 0; i < NB_BUFFER; i++)
{
if (-1 == munmap(vd_info->mem[i], vd_info->buf.length))
error_out("munmap");
}
close(vd_info->camfd);
debug_msg("close OK!/n");
3、显示——SDL
SDL是Simple DirectMedia Layer的简称,是一个自由的跨平台的多媒体开发包,适用于游戏、游戏SDK、演示软件、模拟器、MPEG播放器和其他应用软件。本文将它大材小用,用于显示采集得到的视频数据。
显示的代码片段如下:
SDL_Surface *pscreen = NULL;
SDL_Overlay *overlay = NULL;
SDL_Rect drect;
SDL_Event sdlevent;
SDL_mutex *affmutex = NULL;
unsigned char frmrate;
unsigned char *p = NULL;
uint32 currtime;
uint32 lasttime;
char* status = NULL;
/************* Test SDL capabilities ************/
/* memory leak here */
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "Couldn't initialize SDL: %s/n", SDL_GetError());
exit(1);
}
/* it need to alloc space! */
vd_info = (struct video_info *) calloc(1, sizeof(struct video_info));
/* init the camera,you can change the last three params!!! */
if (v4l2_init(vd_info, V4L2_PIX_FMT_YUYV, 640, 480) < 0)
return EXIT_FAILURE;
pscreen =
SDL_SetVideoMode(vd_info->width, vd_info->height, 0,
SDL_VIDEO_Flags);
overlay =
SDL_CreateYUVOverlay(vd_info->width, vd_info->height,
SDL_YUY2_OVERLAY, pscreen);
/* here?? */
p = (unsigned char *) overlay->pixels[0];
drect.x = 0;
drect.y = 0;
drect.w = pscreen->w;
drect.h = pscreen->h;
lasttime = SDL_GetTicks();
affmutex = SDL_CreateMutex();
/* big loop */
while (vd_info->is_quit)
{
while (SDL_PollEvent(&sdlevent))
{
if (sdlevent.type == SDL_QUIT)
{
vd_info->is_quit = 0;
break;
}
}
currtime = SDL_GetTicks();
if (currtime - lasttime > 0)
{
frmrate = 1000/(currtime - lasttime);
}
lasttime = currtime;
if (v4l2_grab(vd_info) < 0)
{
printf("Error grabbing /n");
break;
}
SDL_LockYUVOverlay(overlay);
/* frame_buffer to p */
memcpy(p, vd_info->frame_buffer,
vd_info->width * (vd_info->height) * 2);
SDL_UnlockYUVOverlay(overlay);
SDL_DisplayYUVOverlay(overlay, &drect); /* dispaly it */
status = (char *)calloc(1, 20*sizeof(char));
sprintf(status, "come on fps:%d",frmrate);
SDL_WM_SetCaption(status, NULL);
SDL_Delay(10);
}
SDL_DestroyMutex(affmutex);
SDL_FreeYUVOverlay(overlay);
v4l2_close(vd_info);
free(status);
free(vd_info);
printf("Clean Up done Quit /n");
SDL_Quit();
return 0;
在红旗操作系统下测试如图3所示。最上面的即为SDL绘制的窗口(至于它向下调用什么图形库就不用理会了),中间的为控制台,它不断输出采集到的帧数以及每一帧的大小。最下面的是emacs。
图3 测试示例图
附:
为了演示emacs的多窗口gdb调试,现附上几个图。算是emacs篇的补充吧。
附图1中,已经加载了可执行文件,并设置了断点(命令为b main emacs,单步后,中间窗口出现红点即为断点处)。
附图2所示的是正在显示图像,左上角即为控制台,出现的信息同上述截图一样。
附图3为结束的情形。重新回到gdb等待命令状态。
此时的emacs五个窗口中各有自己的功能,从上而下,分别为控制台、变量、源代码、帧栈以及断点。
看到了吧?emacs中调试不比VC差吧?
附图1 开始
附图2 显示
附图3 结束
再附:
UVC官网:
http://www.ideasonboard.org/uvc/
V4L2官网: