自己写精简版mjpg-streamer(一):Linux下使用V4L2获取一张MJPEG图片并保存为JPEG文件
前言
从去年九月就有想过把mjpg-streamer的http推流精简化了,当时碰到一个难题,就是mjpeg保存成一张图片,使用电脑打开是看不到的。也就是说,除了把MJPEG提取出来,还需要转换成JPEG才能在电脑上显示出来,这让我使用了YUYV格式,再用libjpeg转换成JPEG,然后再进行推流。显然,这个速度是比直接采集MJPEG慢很多的,在树莓派ZERO W上跑每秒最多只有5帧,这个效果是很差的。
但是这周我又研究了一下Linux下的V4L2(Video For Linux 2),发现直接提取MJPEG并保存为JPEG格式就可以直接在电脑上显示了。这意味着我之前的代码有问题,提取出来的数据不正确。
MJPEG和JPEG的区别
在电脑能直接显示MJPEG数据的JPEG格式的图像,但这并不意味着这两种格式(MJPEG和JPEG)就没有区别,关于这个区别,在谷歌也没有很多的资料,以下是谷歌出来的区别:
1、JPEG是单页文件格式。Motion JPEG是静态照片的JPEG标准的动态视频改编。MJPEG将视频流视为一系列静态照片,单独压缩每个帧,不使用帧间压缩。
2、使用MJPG像素格式,帧率更高(约30 fps),每帧的字节6,7,8,9(从0开始索引)为’J’,‘F’,‘I’,‘F’ 。如果我使用JPEG,帧速率较低(约6 fps),相同的字节为’E’,‘x’,‘i’,‘f’
如上所述,我个人认为JPEG和MJPEG的差距只是在图像信息上有一些区别,但是在图像数据是没有太大的差异的,当然我也不清楚我这样理解是否正确,希望有知道的小伙伴能指正我的错误。
要写一个mjpg-streamer精简版,首先得获取到摄像头图像的数据。
代码
直接上代码,里面都有详细注释的。
/*
* name:camera.c
* author:Hector Cheung
* Time: 4, 9, 2019
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <linux/videodev2.h>
#include<sys/ioctl.h>
#include <setjmp.h>
#include <unistd.h>
#define CAM_DEV "/dev/video0"
#define WIDTH 640 // 图片的宽度
#define HEIGHT 480 // 图片的高度
#define NB_BUFFER 4 //memory block number
struct pic_data
{
unsigned char *tmpbuffer[NB_BUFFER];
unsigned int tmpbytesused[NB_BUFFER];
}pic;
int cam_fd;
/*
Description.:Initalize V4L2 driver.
*/
int v4l2_init(void)
{
int i;
int ret = 0;
// 1、Open camera device
if((cam_fd = open(CAM_DEV,O_RDWR)) == -1)
{
perror("ERROR opening V4L interface.");
return -1;
}
// 2、Judge if the device is a camera device
struct v4l2_capability cam_cap;
if(ioctl(cam_fd,VIDIOC_QUERYCAP,&cam_cap) == -1)
{
perror("Error opening device %s: unable to query device.");
return -1;
}
if((cam_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0)
{
perror("ERROR video capture not supported.");
return -1;
}
// 3、Setting output parameter.
struct v4l2_format v4l2_fmt;
v4l2_fmt.type = V4L2_CAP_VIDEO_CAPTURE;
v4l2_fmt.fmt.pix.width = WIDTH;
v4l2_fmt.fmt.pix.height = HEIGHT;
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
if (ioctl (cam_fd, VIDIOC_S_FMT, &v4l2_fmt) == -1)
{
perror("ERROR camera VIDIOC_S_FMT Failed.");
return -1;
}
// 4、Check whether the parameters are set successfully
if (ioctl (cam_fd, VIDIOC_G_FMT, &v4l2_fmt) == -1)
{
perror("ERROR camera VIDIOC_G_FMT Failed.");
return -1;
}
if (v4l2_fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG)
{
printf("Set VIDIOC_S_FMT sucessful\n");
}
// 5、Require buffer to store image data
struct v4l2_requestbuffers v4l2_req;
v4l2_req.count = NB_BUFFER;
v4l2_req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_req.memory = V4L2_MEMORY_MMAP;
if (ioctl (cam_fd, VIDIOC_REQBUFS, &v4l2_req) == -1)
{
perror("ERROR camera VIDIOC_REQBUFS Failed.");
return -1;
}
// 6、Start memory map
struct v4l2_buffer v4l2_buf;
v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
for(i = 0; i < NB_BUFFER; i++)
{
v4l2_buf.index = i;
if(ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0)
{
perror("Unable to query buffer.");
return -1;
}
pic.tmpbuffer[i] = mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, cam_fd, v4l2_buf.m.offset);
if(pic.tmpbuffer[i] == MAP_FAILED)
{
perror("Unable to map buffer.");
return -1;
}
if(ioctl(cam_fd, VIDIOC_QBUF, &v4l2_buf) < 0)
{
perror("Unable to queue buffer.");
return -1;
}
}
//7、Open stream input
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(cam_fd, VIDIOC_STREAMON, &type) < 0)
{
perror("Unable to start capture.");
return -1;
}
return 0;
}
/*
Description.:Get a jpeg image and save.
*/
int v4l2Grab(void)
{
//8、Get a image
struct v4l2_buffer buff;
buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buff.memory = V4L2_MEMORY_MMAP;
if(ioctl(cam_fd, VIDIOC_DQBUF, &buff) < 0)
{
printf("camera VIDIOC_DQBUF Failed.\n");
return -1;
}
pic.tmpbytesused[buff.index] = buff.bytesused;
printf("size : %d\n",pic.tmpbytesused[buff.index]);
//9、Save image.
int jpg_fd = open("1.jpeg",O_RDWR|O_CREAT,00700);
if(jpg_fd == -1)
{
printf("open ipg Failed!\n ");
return -1;
}
int writesize = write(jpg_fd, pic.tmpbuffer[buff.index], pic.tmpbytesused[buff.index]);
printf("Write successfully size : %d\n",writesize);
close(jpg_fd);
/*
FILE *fp = NULL;
fp = fopen("test.txt", "w");
if(fp != NULL)
{
fwrite(pic[0].tmpbuffer, 1, pic[0].tmpbytesused, fp);
sync();
fclose(fp);
}
*/
//10、Queue the buffers.
if(ioctl(cam_fd, VIDIOC_QBUF, &buff) < 0)
{
printf("camera VIDIOC_QBUF Failed.");
return -1;
}
return 0;
}
/*
Description.:Release resource
*/
int v4l2_close(void)
{
// Remove mmap.
int i;
for(i=0; i<NB_BUFFER; i++)
munmap(pic.tmpbuffer[i],pic.tmpbytesused[i]);
close(cam_fd);
return 0;
}
/*
Description.:main
*/
int main(int argc, char* argv[])
{
v4l2_init();
v4l2Grab();
v4l2_close();
return 0;
}
运行后可以直接在电脑上打开。