版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q1449516487/article/details/91345777
内核配置完并且烧录镜像后,就可以编写应用程序了。这里使用迅为提供的测试程序,该程序的功能是通过OV5640录制一段50帧(大小可设置)的视频,并生成out.yuv视频文件,可以通过yuv播放器或二进制编辑器进行验证。
该程序调用了V4L2,那什么是V4L2呢? V4L2为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。只能在linux下使用。它使程序有发现设备和操作设备的能力。它主要是用一系列的回调函数来实现这些功能。像设置摄像头的频率、帧频、视频压缩格式和图像参数等等。当然也可以用于其他多媒体的开发,如音频等。
可以参考这篇文章学习V4L2——v4l2的学习建议和流程解析
应用程序,YUV播放器及二进制编辑器下载:https://download.csdn.net/download/q1449516487/11231366
下面是应用程序的代码及注释:包含4个文件(build.sh、camera.h、camera.c、main.c)
build.sh:运行build.sh进行编译
#build.sh
arm-none-linux-gnueabi-g++ -o camera camera.cpp main.cpp -static
camera.h:摄像头的h文件
#ifndef CAMERA_H
#define CAMERA_H
#include <sys/types.h>
#define CLEAR(x) memset (&(x), 0, sizeof (x))
//视频IO模式
typedef enum {
IO_METHOD_READ, IO_METHOD_MMAP, IO_METHOD_USERPTR,
} io_method;
//本地缓存区
struct buffer {
void * start;
size_t length;//buffer's length is different from cap_image_size
};
class Camera
{
public:
Camera(char *DEV_NAME,int w,int h, int camer_type);
~Camera();
bool OpenDevice();//打开设备
void CloseDevice();
bool GetBuffer(unsigned char *image);//获取数据流
unsigned int getImageSize();
private:
char dev_name[50];//设备名称
io_method io;//IO
int fd;//描述符
int width;//宽
int height;//高
struct buffer * buffers ;//共享内存
unsigned int n_buffers ;//index
unsigned int cap_image_size ;//to keep the real image size!! //图像大小
bool init_device(void);//初始化设备
//void init_read(unsigned int buffer_size);
bool init_mmap(void);//创建共享内存
//void init_userp(unsigned int buffer_size);
void uninit_device(void);//卸载设备
bool start_capturing(void);//开始捕获
void stop_capturing(void);//停止捕获
void mainloop(unsigned char *image);//
int read_frame(unsigned char *image);//读帧
void close_device(void);//关闭设备
bool open_device(void);//打开设备
bool process_image(unsigned char *image,const void * p);//处理图像
void errno_exit(const char * s);//错误退出
int xioctl(int fd, int request, void * arg);//ioctl
};
#endif // CAMERA_H
camera.c:摄像头的c文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h> /* getopt_long() */
#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <malloc.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
#include "camera.h"
int c_camera_type = 0;
Camera::Camera(char *DEV_NAME, int w, int h, int camera_type)
{
memcpy(dev_name,DEV_NAME,strlen(DEV_NAME));
//设置IO模式
io = IO_METHOD_MMAP;//IO_METHOD_READ;//IO_METHOD_MMAP;
cap_image_size=0;//图像大小
width=w;//宽
height=h;//高
if(1 == camera_type)
{
c_camera_type = 1;//摄像头类型(1:UVC,0:ITU)
}
}
Camera::~Camera(){
}
//返回图像大小
unsigned int Camera::getImageSize(){
return cap_image_size;
}
void Camera::CloseDevice() {
stop_capturing();
uninit_device();
close_device();
}
void Camera::errno_exit(const char * s) {
fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
exit(EXIT_FAILURE);
}
int Camera::xioctl(int fd, int request, void * arg) {
int r;
do
r = ioctl(fd, request, arg);
while (-1 == r && EINTR == errno);
return r;
}
//读取图像帧
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;
//VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,\
v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,\
应用程序会根据 index 确定可用数据的起始地址和范围。
if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return 0;
case EIO:
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
/* Could ignore EIO, see spec. */
/* fall through */
default:
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
errno_exit("VIDIOC_DQBUF");
}
}
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
assert(buf.index < n_buffers);
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
memcpy(image,buffers[0].start,cap_image_size);
//VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,\
即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
errno_exit("VIDIOC_QBUF");
return 1;
}
void Camera::stop_capturing(void) {
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
/* Nothing to do. */
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
errno_exit("VIDIOC_STREAMOFF");
break;
}
}
bool Camera::start_capturing(void) {
unsigned int i;
enum v4l2_buf_type type;
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return false;
}
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return false;
}
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return true;
}
void Camera::uninit_device(void) {
unsigned int i;
switch (io) {
case IO_METHOD_READ:
free(buffers[0].start);
break;
case IO_METHOD_MMAP:
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap(buffers[i].start, buffers[i].length))
errno_exit("munmap");
break;
case IO_METHOD_USERPTR:
for (i = 0; i < n_buffers; ++i)
free(buffers[i].start);
break;
}
free(buffers);
}
//申请一片连续的内存用于缓存视频信息
bool Camera::init_mmap(void) {
struct v4l2_requestbuffers req;
CLEAR (req);
req.count = 4;//count 指定根据图像占用空间大小申请的缓存区个数
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type 为视频捕获模式
req.memory = V4L2_MEMORY_MMAP;//memory 为内存区的使用方式
//VIDIOC_REQBUFS 命令通过结构 v4l2_requestbuffers 请求驱动申请一片连续的内存用于缓存视频信息
if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno) {
fprintf(stderr, "%s does not support "
"memory mapping\n", dev_name);
return false;
} else {
return false;
}
}
if (req.count < 2) {
//缓冲区内存不足
fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name);
return false;
}
//本地视频缓冲区
buffers = (buffer*)calloc(req.count, sizeof(*buffers));
if (!buffers) {
fprintf(stderr, "Out of memory\n");
return false;
}
//req.count表示缓存区个数
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf;
CLEAR (buf);
//bytesused 为缓存已使用空间大小
//flags 为缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)
//timestamp 为时间戳
//sequence为缓存序号
//offset 为当前缓存与内存区起始地址的偏移
//length 为缓存大小
//reserved 一般用于传递物理地址值
//
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type 为视频捕获模式
buf.memory = V4L2_MEMORY_MMAP;//memory 为缓存使用方式
buf.index = n_buffers;//index 为缓存编号
//VIDIOC_QUERYBUF 命令通过结构 v4l2_buffer 查询驱动申请的内存区信息
if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length = buf.length;//buf.length 为缓存大小
buffers[n_buffers].start = mmap(NULL /* start anywhere */, buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */, fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
return false;
}
return true;
}
//初始化摄像头
bool Camera::init_device(void) {
struct v4l2_capability cap;//设备操作模式
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
struct v4l2_format fmt;//视频格式
unsigned int min;
//VIDIOC_QUERYCAP 命令通过结构 v4l2_capability 获取设备支持的操作模式
if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
if (EINVAL == errno) {
fprintf(stderr, "%s is no V4L2 device\n", dev_name);
return false;
} else {
return false;
}
}
//cap.capabilities属性代表设备支持的操作模式,常见的值有 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING 表示是一个视频捕捉设备并且具有数据流控制模式
//V4L2_CAP_VIDEO_CAPTURE表示是一个视频捕捉设备
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
fprintf(stderr, "%s is no video capture device\n", dev_name);
return false;
}
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
//设备IO模式
switch (io){
case IO_METHOD_READ:
//V4L2_CAP_READWRITE表示设备支持读写模式
if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
fprintf(stderr, "%s does not support read i/o\n", dev_name);
return false;
}
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
//V4L2_CAP_STREAMING表示设备支持数据流控模式
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
fprintf(stderr, "%s does not support streaming i/o\n", dev_name);
return false;
}
break;
}
v4l2_input input;//视频输入
memset(&input, 0, sizeof(struct v4l2_input));
input.index = 0;
//VIDIOC_S_INPUT表示设备选择输入源input
int rtn = ioctl(fd, VIDIOC_S_INPUT, &input);
if (rtn < 0) {
printf("VIDIOC_S_INPUT:rtn(%d)\n", rtn);
return false;
}
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
printf("\n");
////////////crop finished!
//////////set the format
CLEAR (fmt);
//V4L2_BUF_TYPE_VIDEO_CAPTURE表示设置视频捕获模式为视频捕捉模式
//在此模式下 fmt 联合体采用域 v4l2_pix_format:
//width 为视频的宽
//height 为视频的高
//pixelformat 为视频数据格式(常见的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)
//bytesperline 为一行图像占用的字节数
//sizeimage 则为图像占用的总字节数
//colorspace 指定设备的颜色空间
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
//V4L2_PIX_FMT_YVU420, V4L2_PIX_FMT_YUV420 — 具有1/2水平和垂直色度分辨率的平面格式,也称为YUV 4:2:0
//V4L2_PIX_FMT_YUYV — 压缩格式与1/2水平色度分辨率,也称为YUV 4:2:2
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YUV420;//V4L2_PIX_FMT_YUYV;
//fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
//fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
{
printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
printf("=====will set fmt to (%d, %d)--", fmt.fmt.pix.width,
fmt.fmt.pix.height);
if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
printf("V4L2_PIX_FMT_YUYV\n");
} else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) {
printf("V4L2_PIX_FMT_YUV420\n");
} else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) {
printf("V4L2_PIX_FMT_NV12\n");
}
}
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
printf("************** %s, line = %d(camera_type = %d)\n", __FUNCTION__, __LINE__, c_camera_type);
if(c_camera_type == 0)//ITU
{
//VIDIOC_S_FMT选择视频格式
if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return false;
}
}
#if 1
if(c_camera_type == 0)
{
fmt.type = V4L2_BUF_TYPE_PRIVATE;
//VIDIOC_S_FMT选择视频格式
if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return false;
}
}
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
#endif
// CLEAR (fmt);
#if 1
//VIDIOC_G_FMT表示查询视频格式
if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return false;
}
#endif
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
printf("=====after set fmt\n");
printf(" fmt.fmt.pix.width = %d\n", fmt.fmt.pix.width);
printf(" fmt.fmt.pix.height = %d\n", fmt.fmt.pix.height);
printf(" fmt.fmt.pix.sizeimage = %d\n", fmt.fmt.pix.sizeimage);
//sizeimage表示图像占用的总字节数
cap_image_size = fmt.fmt.pix.sizeimage;
//bytesperline为一行图像占用的字节数
printf(" fmt.fmt.pix.bytesperline = %d\n", fmt.fmt.pix.bytesperline);
printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
printf("\n");
/* Note VIDIOC_S_FMT may change width and height. */
printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
/* Buggy driver paranoia. */
min = fmt.fmt.pix.width * 2;
//bytesperline为一行图像占用的字节数
if (fmt.fmt.pix.bytesperline < min)
fmt.fmt.pix.bytesperline = min;
min = (unsigned int)width*height*3/2;
printf("min:%d\n",min);
//sizeimage表示图像占用的总字节数
if (fmt.fmt.pix.sizeimage < min)
fmt.fmt.pix.sizeimage = min;
cap_image_size = fmt.fmt.pix.sizeimage;
printf("After Buggy driver paranoia\n");
printf(" >>fmt.fmt.pix.sizeimage = %d\n", fmt.fmt.pix.sizeimage);
printf(" >>fmt.fmt.pix.bytesperline = %d\n", fmt.fmt.pix.bytesperline);
printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
printf("\n");
//申请一片连续的内存用于缓存视频信息
init_mmap();
return true;
}
//关闭设备
void Camera::close_device(void) {
if (-1 == close(fd))
errno_exit("close");
fd = -1;
}
//打开设备
bool Camera::open_device(void) {
struct stat st;//struct stat描述一个linux系统文件系统中的文件属性的结构
if (-1 == stat(dev_name, &st)) {
fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev_name, errno,
strerror(errno));
return false;
}
if (!S_ISCHR(st.st_mode)) {//判断是否字符设备
fprintf(stderr, "%s is no device\n", dev_name);
return false;
}
fd = open(dev_name, O_RDWR /* required */| O_NONBLOCK, 0);//O_RDWR读写 O_NONBLOCK非阻塞,读不到数据返回-1
if (-1 == fd) {
fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_name, errno,
strerror(errno));
return false;
}
return true;
}
//打开设备
bool Camera::OpenDevice(){
if (open_device()) {//打开摄像头
printf("open success\n");
if (init_device()) {//初始化摄像头
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
if (start_capturing())
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return true;
}
else
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
}
} else
printf("open failed\n");
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
return false;
}
//
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) {
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
fprintf(stderr, "select timeout\n");
exit(EXIT_FAILURE);
}
read_frame(image);//读取图像帧
}
main.c:应用程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <time.h>
#include "camera.h"
#define DEV_NAME_LENGTH 50
#define NUM_FRAM 50
int main(int argc, char ** argv) {
//char *dev_name = "/dev/video0"; //ITU
//char *dev_name = "/dev/video1"; //UVC
char *dev_name = NULL;
FILE * outf = 0;
unsigned int image_size;//图像大小
//dev/video0为ITU, dev/video1为UVC
int camera_type = 0;//0:ITU, 1:UVC
int width=640;
int height=480;
if(argc != 3)
{
printf("usage: ./camera /dev/video0 640x480\n");
return 0;
}
dev_name = (char *)malloc(sizeof(char) * DEV_NAME_LENGTH);
if(!dev_name)
{
printf("malloc mem error\n");
return -1;
}
memset(dev_name, 0, sizeof(char) * DEV_NAME_LENGTH);//为新申请的内存做初始化工作
strcpy(dev_name, argv[1]);
if(!strcmp(dev_name, "/dev/video1"))
{
camera_type = 1;//dev/video1为UVC
}
else
{
camera_type = 0;//dev/video0为ITU
}
if(!strcmp(argv[2], "640x480"))
{
width = 640;
height = 480;
}
else if(!strcmp(argv[2], "800x600"))
{
width = 800;
height = 600;
}
else
{
width = 640;
height = 480;
}
outf = fopen("out.yuv", "wb");//打开一个文件,用于保存视频
Camera *camera;
unsigned char image[width*height*2];
clock_t starttime, endtime;
double totaltime;
//创建摄像头对象
camera=new Camera(dev_name,width,height, camera_type);
//打开摄像头
if(!camera->OpenDevice()){
printf("fun:%s, line = %d\n", __FUNCTION__, __LINE__);
return -1;
}
printf("fun:%s, line = %d\n", __FUNCTION__, __LINE__);
image_size=camera->getImageSize();
printf("fun:%s, line = %d\n", __FUNCTION__, __LINE__);
starttime = clock();
//int frames=50;
unsigned int writesize=0;
printf("fun:%s, line = %d\n", __FUNCTION__, __LINE__);
//写NUM_FRAM 帧图像到out.yuv文件中
for(int i=0;i<NUM_FRAM;i++){
if(!camera->GetBuffer(image)){
printf("fun:%s, line = %d\n", __FUNCTION__, __LINE__);
break;
}
printf("fun:%s, line = %d\n", __FUNCTION__, __LINE__);
writesize=fwrite(image,1,image_size,outf);//向文件写数据
//fflush(outf);
printf("frame:%d,writesize:%d\n",i,writesize);
}
printf("fun:%s, line = %d\n", __FUNCTION__, __LINE__);
endtime = clock();
totaltime = (double)( (endtime - starttime)/(double)CLOCKS_PER_SEC );//总时间
printf("time :%f, rate :%f\n",totaltime,NUM_FRAM/totaltime);
camera->CloseDevice();
fclose(outf);
return 0;
}