Exynos4412 OV5640摄像头(三)—— 应用程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 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;
}

猜你喜欢

转载自blog.csdn.net/q1449516487/article/details/91345777