目的是通过tiny4412开发板采集视频通过压缩最后经RTP传输到电脑端的VLC播放器。所以第一步为采集摄像头视频并通过液晶屏显示出来。首先是初始化:先看看V4L2采集视频的初始化
bool Camera::init_device(void) {
v4l2_input input;
memset(&input, 0, sizeof(struct v4l2_input));
input.index = 0;
if (ioctl(fd, VIDIOC_ENUMINPUT, &input) != 0) {
fprintf(stderr, "No matching index found\n");
return false;
}
if (!input.name) {
fprintf(stderr, "No matching index found\n");
return false;
}
if (ioctl(fd, VIDIOC_S_INPUT, &input) < 0) {
fprintf(stderr, "VIDIOC_S_INPUT failed\n");
return false;
}
struct v4l2_fmtdesc fmt1; //v4l2_fmtdesc : 帧格式结构体
int ret;
memset(&fmt1, 0, sizeof(fmt1));//将fmt1结构体填充为0
fmt1.index = 0; //初始化为0,要查询的格式序号
fmt1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
while ((ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt1)) == 0) //显示所有支持的格式
{
fmt1.index++;
printf("{ pixelformat = '%c%c%c%c', description = '%s' }\n",fmt1.pixelformat & 0xFF,
(fmt1.pixelformat >> 8) & 0xFF,(fmt1.pixelformat >> 16) & 0xFF,
(fmt1.pixelformat >> 24) & 0xFF,fmt1.description); // pixelformat;格式32位 description[32];// 格式名称8位
}
/**************************设置当前帧格式************************/
struct v4l2_format fmt; //设置当前格式结构体(v4l2_format包含v4l2_fmtdesc fmt为共用体)
CLEAR (fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//数据流类型
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_NV12;//视频数据存储类型
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))//设置图像格式
return false;
/**************************读取当前帧格式*************************/
if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))
{
return false;
}
else
printf("\nCurrent data format information:\n twidth:%d \n theight:%d \n",
fmt.fmt.pix.width,fmt.fmt.pix.height);
printf(" pixelformat = '%c%c%c%c'\n",fmt.fmt.pix.pixelformat & 0xFF,
(fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF,
(fmt.fmt.pix.pixelformat >> 24) & 0xFF); // pixelformat;格式32位
//原始摄像头数据每帧的大小
cap_image_size = fmt.fmt.pix.sizeimage;
init_mmap();
return true;
}
在这个函数中首先显示摄像头所有支持的输出格式,其次设置当前采集格采集格式为V4L2_PIX_FMT_YUYV即YUYV422。
int main(void) {
int width=640;
int height=480;
unsigned char* image;
camera=new Camera(CAM_DEV, width, height);
if(!camera->OpenDevice()){
printf("Cam Open error\n");
return -1;
}
fb = new Fb(FB_DEV, 80, 0, width, height);
if(!fb->OpenDevice()){
printf("Fb Open error\n");
return -1;
}
fb->Trans(&image);
printf("Waiting for signal SIGINT..\n");
signal(SIGINT, sign_func);
while(1){
if(!camera->GetBuffer(image)){
break;
}
fb->Draw();
}
return 0;
}
程序中有两个类,分别是Camera,Fb类。与V4L2相关操作的都在Camera中。Fb类主要是液晶屏显示。在这里unsigned char* image;这个指针比较重要,在程序中通过fb->Trans(&image); image=show_buffer。show_buffer为显示缓冲区。注意这里函数传递的是二级指针。
在while循环中
while(1){
if(!camera->GetBuffer(image)){
break;
}
fb->Draw();
}
camera->GetBuffer(image)将采集到的视频存放的起始地址为image。接下来看看如何让进行视频采集
bool Camera::GetBuffer(unsigned char *image){
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds);
FD_SET(fd, &fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select(fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r) {
errno_exit("select");
}
if (0 == r) {
fprintf(stderr, "select timeout\n");
exit(EXIT_FAILURE);
}
read_frame(image);
return true;
}
int Camera::read_frame(unsigned char *image) {
struct v4l2_buffer buf;
//CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) //把数据从缓存中读取出来
{
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit("VIDIOC_DQBUF");
}
}
assert(buf.index < CAPTURE_BUFFER_NUMBER);
video_yuyv_to_rgb32((unsigned char*)buffers[0].start,(unsigned char*)image,width,height);
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))//再将其入列,把数据放回缓存队列
errno_exit("VIDIOC_QBUF");
return 1;
}
通过video_yuyv_to_rgb24((unsigned char*)buffers[0].start,(unsigned char*)image,width,height);函数,将采集到视频进行转码,转码后存放于起始地址为image处,正好是触摸屏的显示缓冲区。
接下来看看转码函数。关于转码,这里首先需要了解RGB32和YUYV(yuv422)的编码格式。首先是RGB32,RGB32使用四个字节表示一个像素,排列顺序分别是B,G,R,A(用作Alpha通道或忽略)。YUYV422:排列顺序为Y0U0Y1V0 Y2U1Y3V2(共四个像素点,一个像素点一个Y)。两个Y共用一组UV,单像素占2bytes内存。
所以在下面的转码程序中size=width*height*2;一次for循环转码两个像素点,所以out每次都是加8,其次经过yuvtorgb()函数后,只填充B,G,R;A选择忽略。
//YUYV(yuv422)转RGB32
void Camera:: video_yuyv_to_rgb32(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
unsigned int in, out;
int y0, u, y1, v;
unsigned int pixel24;
unsigned char *pixel = (unsigned char *)&pixel24;
unsigned int size = width*height*2;
for (in = 0, out = 0; in < size; in += 4, out += 8)
{
y0 = yuv[in+0];
u = yuv[in+1];
y1 = yuv[in+2];
v = yuv[in+3];
sign3 = true;
pixel24 = yuvtorgb(y0, u, v);
rgb[out+0] = pixel[0]; //for QT
rgb[out+1] = pixel[1];
rgb[out+2] = pixel[2];
//sign3 = true;
pixel24 = yuvtorgb(y1, u, v);
rgb[out+4] = pixel[0];
rgb[out+5] = pixel[1];
rgb[out+6] = pixel[2];
}
//return 0;
}
下面是yuv转码rgb的函数,这里要注意最后的排列顺序,是b,g,r。转化算法可以百度查到。
int Camera:: yuvtorgb(int y, int u, int v)
{
unsigned int pixel24 = 0;
unsigned char *pixel = (unsigned char *)&pixel24;
int r, g, b;
static long int ruv, guv, buv;
if (sign3)
{
sign3 = false;
ruv = 1159*(v-128);
guv = 380*(u-128) + 813*(v-128);
buv = 2018*(u-128);
}
r = (1164*(y-16) + ruv) / 1000;
g = (1164*(y-16) - guv) / 1000;
b = (1164*(y-16) + buv) / 1000;
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
if (r < 0) r = 0;
if (g < 0) g = 0;
if (b < 0) b = 0;
pixel[0] = b;
pixel[1] = g;
pixel[2] = r;
return pixel24;
}
程序的下载地址为https://download.csdn.net/download/qq_26742291/10406386