C语言高级应用---操作linux下V4L2摄像头应用程序

我们都知道,想要驱动linux下的摄像头,其实很简单,照着V4L2的手册一步步来写,很快就可以写出来,但是在写之前我们要注意改变系统的一些配置,使系统支持framebuffer,在dev下产生fb0这样的节点,这样我们才能在linux系统上操作Camera摄像头,framebuffer在之前的博文已经有说过了,这里就不再提了。

有需要了解framebuffer的那么请点击:http://baike.baidu.com/view/3351639.htm

最重要的,我们需要改一个脚本,在/dev/grub.conf,我们来看看怎么改:

# grub.conf generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE:  You have a /boot partition.  This means that
#          all kernel and initrd paths are relative to /boot/, eg.
#          root (hd0,0)
#          kernel /vmlinuz-version ro root=/dev/sdb2
#          initrd /initrd-[generic-]version.img
#boot=/dev/sdb
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.32-431.el6.i686)
	root (hd0,0)
	kernel /vmlinuz-2.6.32-431.el6.i686 ro root=UUID=2bc12537-d6c1-4e67-b4e5-e9c466205554 nomodeset rd_NO_LUKS  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_MD crashkernel=auto LANG=zh_CN.UTF-8 rd_NO_LVM rd_NO_DM rhgb quiet vga=0x318
	initrd /initramfs-2.6.32-431.el6.i686.img

通常情况下,要让framebuffer生效,要加一句vga=???(这里是参数),简单介绍一下:

我写vga=0x318就是默认就设置为1024x768x24bpp模式。当然还有其它的模式:如下图,根据自己的系统来配置。

色彩 640x400 640x480 800x600 1024x768 1280x1024 1600x1200
4bits ? ? 0x302 ? ? ?
8bits 0x300 0x301 0x303 0x305 0x307 0x31C
15bits ? 0x310 0x313 0x316 0x319 0x31D
16bits ? 0x311 0x314 0x317 0x31A 0x31E
24bits ? 0x312 0x315 0x318 0x31B 0x31F
32bits ? ? ? ? ? ?

配置完成以后,我们先来了解一下V4L2的主要功能。

V4L2就使程序有发现设备和操作设备的能力.它主要是用一系列的回调函数来实现这些功能。像设置摄像头的频率、帧频、视频压缩格式和图像参数等等。当然也可以用于其他多媒体的开发,如音频等。

但是此框架只能运行在Linux操作系统之上。v4L2是针对uvc免驱usb设备的编程框架 ,主要用于采集usb摄像头等,编程模式如下:

采集方式

打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:

extern int ioctl (int __fd, unsigned long int __request, …) __THROW;
__fd:设备的ID,例如刚才用open函数打开视频通道后返回的cameraFd;
__request:具体的命令标志符。
在进行V4L2开发中,一般会用到以下的命令标志符:
VIDIOC_REQBUFS:分配内存
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP:查询驱动功能
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
VIDIOC_S_FMT:设置当前驱动的频捕获格式
VIDIOC_G_FMT:读取当前驱动的频捕获格式
VIDIOC_TRY_FMT:验证当前驱动的显示格式
VIDIOC_CROPCAP:查询驱动的修剪能力
VIDIOC_S_CROP:设置视频信号的边框
VIDIOC_G_CROP:读取视频信号的边框
VIDIOC_QBUF:把数据放回缓存队列
VIDIOC_DQBUF:把数据从缓存中读取出来
VIDIOC_STREAMON:开始视频显示函数
VIDIOC_STREAMOFF:结束视频显示函数
VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。
这些IO调用,有些是必须的,有些是可选择的。

V4L2操作流程:点击这个网址,说得很详细了,这里不多说。

http://baike.baidu.com/view/5494174.htm

接下来我们来看看实战部分,下面是我自己写的程序接口,可以实现视频采集:

1、project.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include "j-yuv.h"
#include "CameralOpt.h"
#include "FrameBufferOpt.h"

#define    WIDTH   640
#define    HIGHT   480

int main(void)
{
	char yuyv[WIDTH*HIGHT*2];
	char bmp[WIDTH*HIGHT*3];

//	set_bmp_header((struct bmp_header_t *)bmp, WIDTH, HIGHT);
	//初始化摄像头
	Init_Cameral(WIDTH , HIGHT );
	//初始化framebuffer
	Init_FrameBuffer(WIDTH , HIGHT ); 

	//开启摄像头
	Start_Cameral();
	//采集一张图片
	int count = 0 ;
	while(1)
	{
		Get_Picture(yuyv);
		yuyv2rgb24(yuyv, bmp, WIDTH, HIGHT);
		Write_FrameBuffer(bmp);
//		printf("count:%d \n" , count++);
	}
	//关闭摄像头
	Stop_Cameral();
	//关闭Framebuffer
	Exit_Framebuffer();
	//退出
	Exit_Cameral();

	return 0;
}

2、juv.h

#ifndef __JYUV_H
#define __JYUV_H

typedef unsigned char  u8;
typedef unsigned short u16;
typedef unsigned int   u32;

#pragma pack(1)
//定义bmp头
struct bmp_header_t{
    u16        magic;
    u32       file_size;
    u32       RESERVED1;
    u32       offset;         //54 bytes 表示54个偏移量

    u32       head_num;    //40
    u32       width;
    u32       height;
    u16       color_planes; //1
    u16       bit_count;
    u32       bit_compression; //0
    u32       image_size; //except the size of header
    u32       h_resolution;
    u32       v_resolution;
    u32       color_num;
    u32       important_colors;
};

#pragma pack()

void set_bmp_header(struct bmp_header_t * header, u32 width, u32 height);
int yuyv2rgb24(u8 *yuyv, u8 *rgb, u32 width, u32 height);

#endif /* __JYUV_H */ 

3、juv.c

#include "j-yuv.h"

#define BIT_COUNT   24

void set_bmp_header(struct bmp_header_t *header, u32 width, u32 height)
{
    header->magic = 0x4d42;
    header->image_size = width * height * BIT_COUNT/8;
    header->file_size = header->image_size + 54;
    header->RESERVED1 = 0;
    header->offset = 54;

    header->head_num = 40;
    header->width = width;
    header->height = height;
    header->color_planes = 1;
    header->bit_count = BIT_COUNT;
    header->bit_compression = 0;
    header->h_resolution = 0;
    header->v_resolution = 0;
    header->color_num = 0;
    header->important_colors = 0;
}
//yuyv转rgb24的算法实现
int yuyv2rgb24(u8 *yuyv, u8 *rgb, u32 width, u32 height)
{
    u32 i, in, rgb_index = 0;
    u8 y0, u0, y1, v1;
    int r, g, b;
    u32 out = 0, x, y;

    for(in = 0; in < width * height * 2; in += 4)
    {
	y0 = yuyv[in+0];
	u0 = yuyv[in+1];
	y1 = yuyv[in+2];
	v1 = yuyv[in+3];

	for (i = 0; i < 2; i++)
	{
		if (i)
			y = y1;
		else
			y = y0;
		r = y + (140 * (v1-128))/100;  //r
		g = y - (34 * (u0-128))/100 - (71 * (v1-128))/100; //g
 		b = y + (177 * (u0-128))/100; //b
		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;

		y = height - rgb_index/width -1;
		x = rgb_index%width;
		rgb[(y*width+x)*3+0] = b;
		rgb[(y*width+x)*3+1] = g;
		rgb[(y*width+x)*3+2] = r;
		rgb_index++;
	}
    }
    return 0;
}

4、FrameBufferOpt.c

#include "FrameBufferOpt.h"

static int Frame_fd ;
static int *FrameBuffer = NULL ;
static int W , H ;

//初始化framebuffer
int Init_FrameBuffer(int Width , int Higth)
{
	W = Width ;
	H = Higth ;
	Frame_fd = open("/dev/fb" , O_RDWR);
	if(-1 == Frame_fd)
	{
		perror("open frame buffer fail");
		return -1 ;
	}

//根本就不用CPU搬运   用DMA做为搬运工
FrameBuffer = mmap(0, 1280*1024*4 , PROT_READ | PROT_WRITE , MAP_SHARED , Frame_fd ,0 );
	if(FrameBuffer == (void *)-1)
	{
		perror("memory map fail");
		return -2 ;
	}
	return 0 ;
}

//写入framebuffer
int Write_FrameBuffer(const char *buffer)
{
	int row  , col ;
	char *p = NULL ;
	for(row = 0 ; row <1024 ; row++)
	{
		for(col = 0 ; col < 1280 ;  col++)
		{
			if((row < H)  && (col < W))
			{
				p = (char *)(buffer + (row * W+ col ) * 3);
				FrameBuffer[row*1280+col] = RGB((unsigned char)(*(p+2)),(unsigned char)(*(p+1)),(unsigned char )(*p));
			}
		}
	}
	return 0 ;
}

//退出framebuffer
int Exit_Framebuffer(void)
{
	munmap(FrameBuffer ,  W*H*4);
	close(Frame_fd);
	return 0 ;
}

5、FrameBufferOpt.h

#ifndef  _FRAMEBUFFEROPT_H
#define  _FRAMEBUFFEROPT_H

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define    RGB(r,g,b)		((r<<16)|(g<<8)|b)

//初始化ramebuffer
int Init_FrameBuffer(int Width , int Higth);

//写数据到framebuffer
int Write_FrameBuffer(const char *buffer);

//退出framebuffer
int Exit_Framebuffer(void);

#endif //_FRAMEBUFFEROPT_H

6、CameralOpt.h

#ifndef  _CAMERALOPT_H
#define  _CAMERALOPT_H

#include <stdio.h>
#include <linux/videodev2.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>

#define   COUNT  3
//初始化摄像头
int Init_Cameral(int Width , int Hight);
int Exit_Cameral(void); //退出摄像头
//摄像头开始采集
int Start_Cameral(void);
int Stop_Cameral(void);//停止摄像头
//获取摄像头的数据
int Get_Picture(char *buffer);

#endif  //_CAMERALOPT_H

7、CameralOpt.c

#include "CameralOpt.h"
int video_fd ;
int length ;
char *yuv[COUNT] ;
struct v4l2_buffer  enqueue  , dequeue ;  //定义出入队的操作结构体成员

int Init_Cameral(int Width , int Hight)
{
	//参数检查
	char *videodevname = NULL ;
	videodevname = "/dev/video0" ; 

	//打开设备
	video_fd = open(videodevname , O_RDWR);
	if(-1 == video_fd )
	{
		perror("open video device fail");
		return -1 ;
	}

	int i ;
	int ret ;
	struct v4l2_format  format ;
	format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
	format.fmt.pix.width  = Width;
	format.fmt.pix.height = Hight;
	format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV ;  //我支持的格式是这个

	ret = ioctl(video_fd , VIDIOC_S_FMT , &format);
	if(ret != 0)
	{
		perror("set video format fail");
		return -2 ;
	}

	//申请buffer,切割成几个部分
	//3
	struct v4l2_requestbuffers  requestbuffer ;
	requestbuffer.count = COUNT ;
	requestbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
	requestbuffer.memory = V4L2_MEMORY_MMAP ; 

	ret = ioctl(video_fd , VIDIOC_REQBUFS , &requestbuffer);
	if(ret != 0)
	{
		perror("request buffer fail ");
		return -3  ;
	}

	//querybuffer
	struct v4l2_buffer querybuffer ;
	querybuffer.type =  V4L2_BUF_TYPE_VIDEO_CAPTURE ;
	querybuffer.memory = V4L2_MEMORY_MMAP ; 

	for(i = 0 ; i < COUNT ; i++)
	{
		querybuffer.index = i ; 	

		ret = ioctl(video_fd , VIDIOC_QUERYBUF , &querybuffer);
		if(ret != 0)
		{
			perror("query buffer fail");
			return -4 ;
		}

//		printf("index:%d length:%d  offset:%d \n" ,
//		querybuffer.index , querybuffer.length , querybuffer.m.offset);
		length = querybuffer.length ; 

		//将摄像头内存印射到进程的内存地址
		yuv[i] = mmap(0,querybuffer.length , PROT_READ | PROT_WRITE , MAP_SHARED , video_fd , querybuffer.m.offset );

		//列队

		struct v4l2_buffer  queuebuffer ;
		queuebuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
		queuebuffer.memory =  V4L2_MEMORY_MMAP ;
		queuebuffer.index = i ; 	

		ret = ioctl(video_fd , VIDIOC_QBUF , &queuebuffer);
		if(ret != 0)
		{
			perror("queuebuffer fail");
			return -5 ;
		}
	}
	//初始化入队出队
	enqueue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
	dequeue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
	enqueue.memory = V4L2_MEMORY_MMAP ;
	dequeue.memory = V4L2_MEMORY_MMAP ; 

	return 0 ;
}

int Exit_Cameral(void)
{
	int i ;
	for(i = 0 ; i < COUNT ; i++)
		munmap(yuv+i , length);
	close(video_fd);
	return 0 ;
}

int Start_Cameral(void)
{
	//开启摄像头
	int ret ;
	int on = 1 ;
	ret = ioctl(video_fd , VIDIOC_STREAMON , &on);
	if(ret != 0)
	{
		perror("start Cameral fail");
		return -1 ;
	}
	return 0 ;
}
int Stop_Cameral(void)
{
	//停止摄像头
	int ret ;
	int off= 1 ;
	ret = ioctl(video_fd , VIDIOC_STREAMOFF, &off);
	if(ret != 0)
	{
		perror("stop Cameral fail");
		return -1 ;
	}
	return 0 ;
}

int Get_Picture(char *buffer)
{
	int ret ;
	//出队
	ret = ioctl(video_fd , VIDIOC_DQBUF , &dequeue);
	if(ret != 0)
	{
		perror("dequeue fail");
		return -1 ;
	}

	//获取图片数据 YUV   yuv[dequeue.index]
	memcpy(buffer , yuv[dequeue.index] , dequeue.length);
//	write(yuyv_fd , yuv[dequeue.index] , dequeue.length);

	enqueue.index = dequeue.index ;
	ret = ioctl(video_fd , VIDIOC_QBUF , &enqueue);
	if(ret != 0)
	{
		perror("enqueue fail");
		return -2 ;
	}
	return 0 ;
}

运行结果:楼主本人,长得丑别喷。

画面其实是一直在动的,只是我拍了一张图片而已。

时间: 2024-10-05 04:55:13

C语言高级应用---操作linux下V4L2摄像头应用程序的相关文章

在linux下如何编译C++程序

一.GCC(GNU Compiler Collection)是linux下最主要的编译工具,GCC不仅功能非常强大,结构也异常灵活.它可以通过不同的前端模块来支持各种语言,如Java.Fortran.Pascal.Modula-3和Ada g++是GCC中的一个工具,专门来编译C++语言的. GCC的参数有:( 也是分步实现) -E  让GCC在预处理结束后停止编译  g++ -E hello.cpp  -o  hello.i -c  将hello.i编译成目标代码 g++  -c  hello

Linux 下查看某一个程序所使用的内存方法介绍

Linux 下查看某一个程序所使用的内存方法介绍 在 Linux 上进行开发和运营维护的时候,免不了要查看某一个程序所占用内存的情况.常用方法总结如下(注意第四种方法): 第一种:ps -aux | grep process_name 举例如下:现打算监控/usr/bin/sshd所占的内存,首先需找到pid,然后使用top进行有目标的监控,RES即为内存值,见下两图: top -p 1231,截图如下:    第二种:top -p pid 查看程序的情况 如上图所示! 第三种:cat /pro

Linux下部署Java应用程序

Linux软件安装的特点: 1.Linux中没有注册表的概念,因此要想删除某个软件直接删除软件目录就可以了. 2.软件的安装有些需要Linux软件包的支持,在Linux中使用rpm命令管理软件包. 3.Linux中软件安装的过程很迅速,并且软件的运行速度也很快. 4.Linux中权限管理很严格,因此安装时需要对安装介质进行权限配置. 体验Linux下的Java开发 编译器->字节码->(虚拟机)本地机器码->执行 Linux系统与Windows系统最大的区别: Windows路径分隔是&

linux之V4L2摄像头应用流程【转】

本文转载自:http://blog.csdn.net/tommy_wxie/article/details/11486907 对于v4l2,上次是在调试收音机驱动的时候用过,其他也就只是用i2c配置一些寄存器就可以了.那时只是粗粗的了解了,把收音机当作v4l2的设备后会在/dev目录下生成一个radio的节点.然后就可以操作了.后来就没怎么接触了.这周,需要调试下usb的摄像头.因为有问题,所以就要跟进,于是也就要开始学习下linux的v4l2了.看到一篇很不错的文章,下面参考这篇文章,加上自己

不错的linux下通用的java程序启动脚本(转载)

转自:http://www.cnblogs.com/langtianya/p/4164151.html 虽然写起动shell的频率非常不高...但是每次要写都要对付一大堆的jar文件路径,新加jar包也必须要修改起动shell. 在网上找到一个挺好的通用shell脚本. 只需要修改一些配置变量,就可以用来做起动脚本了. 并且除了能起动.还支持关闭.重启.查看是否正在运行的功能. 原文地址:http://www.tudaxia.com/archives/10 start函数中,nohup部分其实也

不错的linux下通用的java程序启动脚本

虽然写起动shell的频率非常不高...但是每次要写都要对付一大堆的jar文件路径,新加jar包也必须要修改起动shell. 在网上找到一个挺好的通用shell脚本. 只需要修改一些配置变量,就可以用来做起动脚本了. 并且除了能起动.还支持关闭.重启.查看是否正在运行的功能. 原文地址:http://www.tudaxia.com/archives/10 start函数中,nohup部分其实也可以提出来放入一个配置变量中.这里没有修改直接贴上作者的原文 #!/bin/sh #该脚本为Linux下

Linux下时间范围判断的程序流程及其C代码实现

一.概述 在实际的软件开发项目中,经常会遇到需要判断时间范围的情况.例如,某软件要在某段时间内执行对过期数据的清理,在其它时间段不执行.为了体现程序的灵活性,一般都是在配置文件中设置时间段的始末值,这样可根据软件的实际安装环境进行配置.本文介绍判断当前时间是否在配置的时间范围内的程序流程,并给出了C代码实现.具体的需求描述如下: 在配置文件中按需配置某一开始时间和结束时间,编写程序判断当前系统时间是否在配置时间的范围之内.配置的时间格式为:HH:MM(小时:分),如08:30表示8点30分. 二

[科技]NOI Linux下的对拍程序

我们知道,在$Windows$环境下用$cmd$里的$FC$函数实现对拍(放到一个目录下): #include <cstdlib> int main(){ while(true){ system("make.exe >make.txt"); system("Force.exe <make.txt> Force.txt"); system("need.exe <make.txt> need.txt"); i

在Linux下运转C言语程序

市情上罕见的Linux多是刊行版本,典型的Linux刊行版包括了Linux内核.桌面情况和各类常用的必备工具,国际运用较多的是Ubuntu(乌班图).CentOS.Deepin(深度Linux).本教程以Deepin为例来讲述若何编译C程序. Gedit Gedit是一个复杂适用的文本编纂器,具有美丽的界面,支撑语法高亮,比 Vim 更易上手.本教程将Gedit作为C代码编纂器.Gedit的装置: sudo apt-add-repository ppa:ubuntu-on-rails/ppa