USB摄像头驱动--LCD显示摄像头图像(附Makefile分析)

对于一个应用程序,最重要的是明白目的是什么:将摄像头的数据解析出来,按一帧一个图片的方式将数据传到LCD的Framebuffer中去(如果LCD没有自动将Framebuffer中的数据刷到LCD上还需要进行flush操作)

1.准备工作

将USB的数据传入开发板中内核,所以USB摄像头是插在开发板的USB接口上。

在开发板中的内核,需要加入LCD驱动、背光驱动、UVC驱动。
驱动的使用方法有两种:

  1. 手动加载:每次上电后进行insmode xx.ko。这种方式适合调式产品时进行。
  2. 自动加载:将LCD驱动、背光驱动、UVC驱动加载到内核配置中(make menuconfig,找到相应的驱动,在选择按下y(y表示编译进内个,m表示编译成模块,n表示不变异)),这样进入系统后就已经有了这三个驱动

1.1安装工具链arm-linux-gcc

  1. 安装工具链: sudo tar xjf arm-linux-gcc-4.3.2.tar.bz2 -C /

  2. 设置环境变量:sudo vi /etc/environment :
    PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.3.2/bin"

  3. 编译内核:tar xjf linux-3.4.2.tar.bz2
    cd linux-3.4.2

  4. 方式一:打补丁:patch -p1 < …/linux-3.4.2_camera_jz2440.patch
    cp config_ok .config
    make uImage

方式二:打补丁:patch -p1 < …/linux-3.4.2_100ask.patch
把 lcd_4.3.c 复制到 /work/projects/linux-3.4.2/drivers/video
修改/work/projects/linux-3.4.2/drivers/video/Makefile
#obj- ( C O N F I G F B S 3 C 2410 ) + = s 3 c 2410 f b . o o b j − (CONFIG_FB_S3C2410) += s3c2410fb.o obj- (CONFIGFBS3C2410)+=s3c2410fb.oobj(CONFIG_FB_S3C2410) += lcd_4.3.o

把dm9dev9000c.c、dm9000.h复制到/work/projects/linux-3.4.2/drivers/net/ethernet/davicom
修改/work/projects/linux-3.4.2/drivers/net/ethernet/davicom/Makefile

cp config_ok .config
make menuconfig
[※] Multimedia support —>
[※] Video For Linux
[※] Video capture adapters (NEW) —>
[※] V4L USB devices (NEW) —>
[※] USB Video Class (UVC)

  1. make uImage
  2. cp arch/arm/boot/uImage /work/nfs_root/uImage_new

1.2最小文件系统的制作

使用之前做好的根文件系统
cd /work/nfs_root
sudo tar xjf fs_mini_mdev_new.tar.bz2
sudo chown book:book fs_mini_mdev_new (赋予权限)

1.3用新内核、新文件系统启动开发板

启动开发板至UBOOT
设置UBOOT的环境变量:
set ipaddr 192.168.1.110(开发板ip)
set bootcmd ‘nfs 32000000 192.168.1.124:/work/nfs_root/uImage_new; bootm 32000000’(192.168.1.124是linux的IP)(注意bootcmd后面的内容需要两个单引号引)
set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.124:/work/nfs_root/fs_mini_mdev_new ip=192.168.1.110(/work/nfs_root/fs_mini_mdev_new是根文件系统的位置,这里是NFS网络挂载)
save
boot

2.应用流程

  1. 从摄像头中获取video_buf数据,这里的数据可能有多种分辨率以及多种格式 (YUV,MJPEG,RGB)
  2. s3c2440开发板仅支持RGB格式的数据,因此需要一个数据格式转换(YUV2RGB,MJPEG2RGB,RGB2RGB)
  3. 转换后的数据由于可能存在多种分辨率,因此需要确定LCD的分辨率(disp方面的函数)以及将数据转换(缩放的函数)
  4. 然后将缩放后的数据放到显存中去
  5. 最后LCD控制器会将显存中的数据自动搬运到LCD上去。
    在这里插入图片描述
    对于应用编程,编写者一定要有这样的思想:面向对象编程,也就是把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数,在C语言中常常用结构体(struct)来实现。

编程时就是讲每一个实体抽象出一些共性与个性,共性作为公布出来接口,个性作为自己的私有。将共性一个一个串联起来成为一个链表,在上层想要访问该实体时,必须先去管理层寻找这个实体,再从接口进行数据的访问以及读写。
在这里插入图片描述

3.获取摄像头数据

在linux的眼里,所有事物都是文件 ,摄像头设备也是一个文件,打开文件需要 文件句柄,这个摄像头支持哪些格式、分辨率,buf信息、操作函数等

/*由于相互引用,所以需要申明*/
struct VideoDevice;
struct VideoOpr;
typedef struct VideoDevice T_VideoDevice, *PT_VideoDevice;
typedef struct VideoOpr T_VideoOpr, *PT_VideoOpr;

struct VideoDevice {
    int iFd;		//文件句柄
    int iPixelFormat;		 //像素格式
    int iWidth;			/分辨率:宽*高
    int iHeight;

    int iVideoBufCnt;		//buf数量
    int iVideoBufMaxLen;		//每个buf最大长度
    int iVideoBufCurIndex;		 //当前buf索引
    unsigned char *pucVideBuf[NB_BUFFER];		//每个video buf的地址

    /* 函数 */
    PT_VideoOpr ptOpr;
};
//摄像头设备的操作函数
struct VideoOpr {
    char *name;
    int (*InitDevice)(char *strDevName, PT_VideoDevice ptVideoDevice);
    int (*ExitDevice)(PT_VideoDevice ptVideoDevice);
    int (*GetFrame)(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
    int (*GetFormat)(PT_VideoDevice ptVideoDevice);
    int (*PutFrame)(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
    int (*StartDevice)(PT_VideoDevice ptVideoDevice);
    int (*StopDevice)(PT_VideoDevice ptVideoDevice);
    struct VideoOpr *ptNext;
};
/* 图片的象素数据 */
typedef struct PixelDatas {
	int iWidth;   /* 宽度: 一行有多少个象素 */
	int iHeight;  /* 高度: 一列有多少个象素 */
	int iBpp;     /* 一个象素用多少位来表示 */
	int iLineBytes;  /* 一行数据有多少字节 */
	int iTotalBytes; /* 所有字节数 */ 
	unsigned char *aucPixelDatas;  /* 象素数据存储的地方 */
}T_PixelDatas, *PT_PixelDatas;


/*摄像头的数据*/
typedef struct VideoBuf {
    T_PixelDatas tPixelDatas; //图片像素的数据
    int iPixelFormat;		  //像素的格式
}T_VideoBuf, *PT_VideoBuf;

3.1 管理层–video_manager.c

video_manager的主要功能是注册设备,将设备挂载到链表上,遍历链表等;

/*定义一个链表头部*/
static PT_VideoOpr g_ptVideoOprHead = NULL;
/*注册设备:将设备挂载到链表上*/
int RegisterVideoOpr(PT_VideoOpr ptVideoOpr)
{
    PT_VideoOpr ptTmp;
    if(!g_ptVideoOprHead)
    {
        g_ptVideoOprHead   = ptVideoOpr;
		ptVideoOpr->ptNext = NULL;
    }
    else
    {
        ptTmp = g_ptVideoOprHead;
        while(ptTmp->ptNext)
        {
            ptTmp = ptTmp->ptNext;
        }
        ptTmp->ptNext = ptVideoOpr;
        ptVideoOpr->ptNext = NULL;
    }
    return 0;
    
}
/*遍历链表:显示支持的设备名*/
void ShowVideoOpr(void)
{
    int i = 0;
    PT_VideoOpr ptTmp = g_ptVideoOprHead;
    while (ptTmp)
    {
        printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
    }
    
}
/*找到应用层需要的设备*/
PT_VideoOpr GetVideoOpr(char *pcName)
{
    PT_VideoOpr ptTmp = g_ptVideoOprHead;
    while (ptTmp)
    {
        if(strcmp(ptTmp->name, pcName) == 0)
        {
            return ptTmp;
        }
        ptTmp = ptTmp->ptNext;
    }
    return NULL;
}
/*所有设备初始化*/
int VideoDeviceInit(char *strDevName, PT_VideoDevice ptVideoDevice)
{
    int iError;
    PT_VideoOpr ptTmp = g_ptVideoOprHead;
    while (ptTmp)
    {
        iError = ptTmp->InitDevice(strDevName,ptVideoDevice);
        if(!iError)
        {
            return 0;
        }
        ptTmp = ptTmp->ptNext;
    }
    return -1;
}
/*初始化*/
int VideoInit(void)
{
    int iError;
    iError = V4l2Init();
    if(iError)
    {
        DBG_PRINTF("V4l2Init error!\n");
		return -1;
    }
    
    return 0;
}

3.2 摄像头设备–v4l2.c

首先分配设置注册一个结构体

/* 构造一个VideoOpr结构体 */
static T_VideoOpr g_tV4l2VideoOpr = {
    .name        = "v4l2",
    .InitDevice  = V4l2InitDevice,
    .ExitDevice  = V4l2ExitDevice,
    .GetFormat   = V4l2GetFormat,
    .GetFrame    = V4l2GetFrameForStreaming,
    .PutFrame    = V4l2PutFrameForStreaming,
    .StartDevice = V4l2StartDevice,
    .StopDevice  = V4l2StopDevice,
};

/* 注册这个结构体 */
int V4l2Init(void)
{
    return RegisterVideoOpr(&g_tV4l2VideoOpr);
}

应用调用各种ioctl函数进行数据的读取:

/* open
 * VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)
 * VIDIOC_ENUM_FMT 查询支持哪种格式
 * VIDIOC_S_FMT    设置摄像头使用哪种格式
 * VIDIOC_REQBUFS  申请buffer
 对于 streaming:
 * VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap
 * VIDIOC_QBUF     放入队列
 * VIDIOC_STREAMON 启动设备
 * poll            等待有数据
 * VIDIOC_DQBUF    从队列中取出
 * 处理....
 * VIDIOC_QBUF     放入队列
 * ....
 对于read,write:
    read
    处理....
    read
 * VIDIOC_STREAMOFF 停止设备
 *
 */

对于streaming接口,使用v4l2_get_frame_streaming()和v4l2_put_frame_streaming()来获取数据。
首先poll()查询是否有数据,使用VIDIOC_DQBUF从队列取出数据,最后再VIDIOC_QBUF放入队列。

对于streaming接口,使用v4l2_get_frame_readwrite()来获取数据。
在这里插入图片描述

#include <config.h>
#include <video_manager.h>
#include <disp_manager.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>

static int g_aiSupportedFormats[] = {V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_RGB565};

static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static T_VideoOpr g_tV4l2VideoOpr;

static int isSupportThisFormat(int iPixelFormat)
{
    int i;
    for (i = 0; i < sizeof(g_aiSupportedFormats)/sizeof(g_aiSupportedFormats[0]); i++)
    {
        if (g_aiSupportedFormats[i] == iPixelFormat)
            return 1;
    }
    return 0;
}

/* 参考 luvcview */

/* open
 * VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)
 * VIDIOC_ENUM_FMT 查询支持哪种格式
 * VIDIOC_S_FMT    设置摄像头使用哪种格式
 * VIDIOC_REQBUFS  申请buffer
 对于 streaming:
 * VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap
 * VIDIOC_QBUF     放入队列
 * VIDIOC_STREAMON 启动设备
 * poll            等待有数据
 * VIDIOC_DQBUF    从队列中取出
 * 处理....
 * VIDIOC_QBUF     放入队列
 * ....
 对于read,write:
    read
    处理....
    read
 * VIDIOC_STREAMOFF 停止设备
 *
 */

static int V4l2InitDevice(char *strDevName, PT_VideoDevice ptVideoDevice)
{
    int i;
    int iFd;
    int iError;
    struct v4l2_capability tV4l2Cap;
	struct v4l2_fmtdesc tFmtDesc;
    struct v4l2_format  tV4l2Fmt;
    struct v4l2_requestbuffers tV4l2ReqBuffs;
    struct v4l2_buffer tV4l2Buf;

    int iLcdWidth;
    int iLcdHeigt;
    int iLcdBpp;

    iFd = open(strDevName, O_RDWR);
    if (iFd < 0)
    {
        DBG_PRINTF("can not open %s\n", strDevName);
        return -1;
    }
    ptVideoDevice->iFd = iFd;

    iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
    memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
    iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
    if (iError) {
    	DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
    	goto err_exit;
    }

    if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
    	DBG_PRINTF("%s is not a video capture device\n", strDevName);
        goto err_exit;
    }

	if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
	    DBG_PRINTF("%s supports streaming i/o\n", strDevName);
	}
    
	if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
	    DBG_PRINTF("%s supports read i/o\n", strDevName);
	}

	memset(&tFmtDesc, 0, sizeof(tFmtDesc));
	tFmtDesc.index = 0;
	tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {
        if (isSupportThisFormat(tFmtDesc.pixelformat))
        {
            ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
            break;
        }
		tFmtDesc.index++;
	}

    if (!ptVideoDevice->iPixelFormat)
    {
    	DBG_PRINTF("can not support the format of this device\n");
        goto err_exit;        
    }

    
    /* set format in */
    GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
    memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
    tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;
    tV4l2Fmt.fmt.pix.width       = iLcdWidth;
    tV4l2Fmt.fmt.pix.height      = iLcdHeigt;
    tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;

    /* 如果驱动程序发现无法某些参数(比如分辨率),
     * 它会调整这些参数, 并且返回给应用程序
     */
    iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); 
    if (iError) 
    {
    	DBG_PRINTF("Unable to set format\n");
        goto err_exit;        
    }
    ptVideoDevice->iWidth  = tV4l2Fmt.fmt.pix.width;
    ptVideoDevice->iHeight = tV4l2Fmt.fmt.pix.height;

    /* request buffers */
    memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
    tV4l2ReqBuffs.count = NB_BUFFER;
    tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;

    iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
    if (iError) 
    {
    	DBG_PRINTF("Unable to allocate buffers.\n");
        goto err_exit;        
    }
    
    ptVideoDevice->iVideoBufCnt = tV4l2ReqBuffs.count;
    if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING)
    {
        /* map the buffers */
        for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) 
        {
        	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        	tV4l2Buf.index = i;
        	tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
        	iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
        	if (iError) 
            {
        	    DBG_PRINTF("Unable to query buffer.\n");
        	    goto err_exit;
        	}

            ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
        	ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,
        			  tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
        			  tV4l2Buf.m.offset);
        	if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED) 
            {
        	    DBG_PRINTF("Unable to map buffer\n");
        	    goto err_exit;
        	}
        }        

        /* Queue the buffers. */
        for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) 
        {
        	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        	tV4l2Buf.index = i;
        	tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
        	iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
        	if (iError)
            {
        	    DBG_PRINTF("Unable to queue buffer.\n");
        	    goto err_exit;
        	}
        }
        
    }
    else if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE)
    {
        g_tV4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;
        g_tV4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;
        
        /* read(fd, buf, size) */
        ptVideoDevice->iVideoBufCnt  = 1;
        /* 在这个程序所能支持的格式里, 一个象素最多只需要4字节 */
        ptVideoDevice->iVideoBufMaxLen = ptVideoDevice->iWidth * ptVideoDevice->iHeight * 4;
        ptVideoDevice->pucVideBuf[0] = malloc(ptVideoDevice->iVideoBufMaxLen);
    }

    ptVideoDevice->ptOpr = &g_tV4l2VideoOpr;
    return 0;
    
err_exit:    
    close(iFd);
    return -1;    
}

static int V4l2ExitDevice(PT_VideoDevice ptVideoDevice)
{
    int i;
    for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
    {
        if (ptVideoDevice->pucVideBuf[i])
        {
            munmap(ptVideoDevice->pucVideBuf[i], ptVideoDevice->iVideoBufMaxLen);
            ptVideoDevice->pucVideBuf[i] = NULL;
        }
    }
        
    close(ptVideoDevice->iFd);
    return 0;
}
    
static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    struct pollfd tFds[1];
    int iRet;
    struct v4l2_buffer tV4l2Buf;
            
    /* poll */
    tFds[0].fd     = ptVideoDevice->iFd;
    tFds[0].events = POLLIN;

    iRet = poll(tFds, 1, -1);
    if (iRet <= 0)
    {
        DBG_PRINTF("poll error!\n");
        return -1;
    }
    
    /* VIDIOC_DQBUF */
    memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);
    if (iRet < 0) 
    {
    	DBG_PRINTF("Unable to dequeue buffer.\n");
    	return -1;
    }
    ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index;

    ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;
    ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;
    ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;
    ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 :  \
                                        0;
    ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;
    ptVideoBuf->tPixelDatas.iTotalBytes   = tV4l2Buf.bytesused;
    ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index];    
    return 0;
}


static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    /* VIDIOC_QBUF */
    struct v4l2_buffer tV4l2Buf;
    int iError;
    
	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
	tV4l2Buf.index  = ptVideoDevice->iVideoBufCurIndex;
	tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
	iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf);
	if (iError) 
    {
	    DBG_PRINTF("Unable to queue buffer.\n");
	    return -1;
	}
    return 0;
}

static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    int iRet;

    iRet = read(ptVideoDevice->iFd, ptVideoDevice->pucVideBuf[0], ptVideoDevice->iVideoBufMaxLen);
    if (iRet <= 0)
    {
        return -1;
    }
    
    ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;
    ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;
    ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;
    ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565)? 16 : \
                                          0;
    ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;
    ptVideoBuf->tPixelDatas.iTotalBytes   = iRet;
    ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[0];    
    
    return 0;
}


static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    return 0;
}

static int V4l2StartDevice(PT_VideoDevice ptVideoDevice)
{
    int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int iError;

    iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);
    if (iError) 
    {
    	DBG_PRINTF("Unable to start capture.\n");
    	return -1;
    }
    return 0;
}
    
static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
{
    int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int iError;

    iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMOFF, &iType);
    if (iError) 
    {
    	DBG_PRINTF("Unable to stop capture.\n");
    	return -1;
    }
    return 0;
}

static int V4l2GetFormat(PT_VideoDevice ptVideoDevice)
{
    return ptVideoDevice->iPixelFormat;
}


/* 构造一个VideoOpr结构体 */
static T_VideoOpr g_tV4l2VideoOpr = {
    .name        = "v4l2",
    .InitDevice  = V4l2InitDevice,
    .ExitDevice  = V4l2ExitDevice,
    .GetFormat   = V4l2GetFormat,
    .GetFrame    = V4l2GetFrameForStreaming,
    .PutFrame    = V4l2PutFrameForStreaming,
    .StartDevice = V4l2StartDevice,
    .StopDevice  = V4l2StopDevice,
};

/* 注册这个结构体 */
int V4l2Init(void)
{
    return RegisterVideoOpr(&g_tV4l2VideoOpr);
}


4.格式转换

前面的UVC驱动,通过USB设备描述符知道了摄像头图像数据格式是MJPEG,而LCD只支持RGB格式,且前面LCD驱动,设置的LCD为RGB32格式。因此这里需要把MJPEG转换成RGB32格式。

使用结构体video_convert来表示一种转换,包含名字、判断是否支持转换、转换等:

typedef struct VideoConvert {
    char *name;
    int (*isSupport)(int iPixelFormatIn, int iPixelFormatOut);
    int (*Convert)(PT_VideoBuf ptVideoBufIn, PT_VideoBuf ptVideoBufOut);
    int (*ConvertExit)(PT_VideoBuf ptVideoBufOut);
    struct VideoConvert *ptNext;
}T_VideoConvert, *PT_VideoConvert;

4.1 管理层–convert_manager.c

与video_manager.h构造方式类似
这里有三类转换:MJPEG转RGB、YUV转RGB、RGB转RGB,将它们都放到链表中,通过get_video_convert_format()传入待转换的格式,从链表中依次查询谁支持该转换,如果支持,就得到p_video_convert,就可以调用到对应的操作函数。

4.2 对象1–mjpeg2rgb.c

解压操作过程如下:
1、分配jpeg对象结构体空间,并初始化
2、指定解压数据源
3、获取解压文件信息
4、为解压设定参数,包括图像大小和颜色空间
5、开始解压缩 6、取数据并显示
7、解压完毕
8、释放资源和退出程序

  1. 分配jpeg对象结构体空间、并初始化
    解压缩过程中使用的JPEG对象是jpeg_decompress_struct结构体。
    同时还需要定义一个用于错误处理的结构体对象,IJG中标准的错误结构体是jpeg_error_mgr。
struct jpeg_decompress_struct tDInfo;
	//struct jpeg_error_mgr tJErr;

绑定tJErr错误结构体至jpeg对象结构体。

tDInfo.err = jpeg_std_error(&tJErr);

这个标准的错误处理结构将使程序在出现错误时调用exit()退出程序,如果不希望使用标准的错误处理方式,则可以通过自定义退出函数的方法自定义错误处理结构。

初始化cinfo结构体。

jpeg_create_decompress(&tDInfo);
  1. 指定解压数据源
FILE * infile;
if ((infile = fopen(argv[1], "rb")) == NULL) {
	fprintf(stderr, "can't open %s\n", argv[1]);
	return -1;
}
jpeg_stdio_src(&tDInfo, ptFileMap->tFp);

3.获取解压文件信息
将图像的缺省信息填充到tDInfo结构中以便程序使用。

iRet = jpeg_read_header(&tDInfo, TRUE);

此时,常见的可用信息包括图像的:
宽cinfo.image_width,高cinfo.image_height,色彩空间cinfo.jpeg_color_space,颜色通道数cinfo.num_components等。

4、为解压设定参数,包括图像大小和颜色空间
比如可以设定解出来的图像的大小,也就是与原图的比例。
使用scale_num和scale_denom两个参数,解出来的图像大小就是scale_num/scale_denom,但是IJG当前仅支持1/1, 1/2, 1/4,和1/8这几种缩小比例。

/*原图大小*/
tDInfo.scale_num = tDInfo.scale_denom = 1;

也可以设定输出图像的色彩空间,即cinfo.out_color_space,可以把一个原本彩色的图像由真彩色JCS_RGB变为灰度JCS_GRAYSCALE。

	
tDInfo.out_color_space=JCS_GRAYSCALE;  

5、开始解压缩
根据设定的解压缩参数进行图像解压缩操作。

jpeg_start_decompress(&tDInfo);

在完成解压缩操作后,会将解压后的图像信息填充至cinfo结构中。比如,输出图像宽度tDInfo.output_width,输出图像高度tDInfo.output_height,每个像素中的颜色通道数tDInfo.output_components(比如灰度为1,全彩色为3)等。

iRowStride = tDInfo.output_width * tDInfo.output_components;
	aucLineBuffer = malloc(iRowStride);

一般情况下,这些参数是在jpeg_start_decompress后才被填充到cinfo中的,如果希望在调用jpeg_start_decompress之前就获得这些参数,可以通过调用jpeg_calc_output_dimensions()的方法来实现。
手动将数据保存下来以便其他使用

	ptPixelDatas->iWidth  = tDInfo.output_width;
	ptPixelDatas->iHeight = tDInfo.output_height;
	//ptPixelDatas->iBpp    = iBpp;
	ptPixelDatas->iLineBytes    = ptPixelDatas->iWidth * ptPixelDatas->iBpp / 8;
    ptPixelDatas->iTotalBytes   = ptPixelDatas->iHeight * ptPixelDatas->iLineBytes;
	if (NULL == ptPixelDatas->aucPixelDatas)
	{
	    ptPixelDatas->aucPixelDatas = malloc(ptPixelDatas->iTotalBytes);
	}

6、取数据
解开的数据是按照行取出的,数据像素按照scanline来存储,scanline是从左到右,从上到下的顺序,每个像素对应的各颜色或灰度通道数据是依次存储。
比如一个24-bit RGB真彩色的图像中,一个scanline中的数据存储模式是R,G,B,R,G,B,R,G,B,…,每条scanline是一个JSAMPLE类型的数组,一般来说就是 unsigned char,定义于jmorecfg.h中。
除了JSAMPLE,图像还定义了JSAMPROW和JSAMPARRAY,分别表示一行JSAMPLE和一个2D的JSAMPLE数组。

// 循环调用jpeg_read_scanlines来一行一行地获得解压的数据
	while (tDInfo.output_scanline < tDInfo.output_height) 
	{
        /* 得到一行数据,里面的颜色格式为0xRR, 0xGG, 0xBB */
		(void) jpeg_read_scanlines(&tDInfo, &aucLineBuffer, 1);

		// 转到ptPixelDatas去
		CovertOneLine(ptPixelDatas->iWidth, 24, ptPixelDatas->iBpp, aucLineBuffer, pucDest);
		pucDest += ptPixelDatas->iLineBytes;
	}

然后实现CovertOneLine()函数,将解压后的数据转换为RGB565数据

7.转换错误处理函数
自定义的libjpeg库出错处理函数默认的错误处理函数是让程序退出,我们当然不会使用它
参考libjpeg里的bmp.c编写了这个错误处理函数
输入参数: ptCInfo - libjpeg库抽象出来的通用结构体

static void MyErrorExit(j_common_ptr ptCInfo)
{
    static char errStr[JMSG_LENGTH_MAX];
    
	PT_MyErrorMgr ptMyErr = (PT_MyErrorMgr)ptCInfo->err;

    /* Create the message */
    (*ptCInfo->err->format_message) (ptCInfo, errStr);
    DBG_PRINTF("%s\n", errStr);

	longjmp(ptMyErr->setjmp_buffer, 1);
}

前面LCD驱动里,将LCD设置为了RGB32(实际还是RGB24,多出来的没有使用),而摄像头采集的数据格式为RGB24,因此需要RGB24转RGB32。

如果源bpp和目标bpp一致,直接memcpy()复制,长度就是宽的像素个数x每个像素由3*8位构成/8位构成一字节:width*(8+8+8)/8=width*3
如果是24BPP转32BPP,需要把源数据变长:
在这里插入图片描述

static int CovertOneLine(int iWidth, int iSrcBpp, int iDstBpp, unsigned char *pudSrcDatas, unsigned char *pudDstDatas)
{
	unsigned int dwRed;
	unsigned int dwGreen;
	unsigned int dwBlue;
	unsigned int dwColor;

	unsigned short *pwDstDatas16bpp = (unsigned short *)pudDstDatas;
	unsigned int   *pwDstDatas32bpp = (unsigned int *)pudDstDatas;

	int i;
	int pos = 0;

	if (iSrcBpp != 24)
	{
		return -1;
	}

	if (iDstBpp == 24)
	{
		memcpy(pudDstDatas, pudSrcDatas, iWidth*3);
	}
	else
	{
		for (i = 0; i < iWidth; i++)
		{
			dwRed   = pudSrcDatas[pos++];
			dwGreen = pudSrcDatas[pos++];
			dwBlue  = pudSrcDatas[pos++];
			if (iDstBpp == 32)
			{
				dwColor = (dwRed << 16) | (dwGreen << 8) | dwBlue;
				*pwDstDatas32bpp = dwColor;
				pwDstDatas32bpp++;
			}
			else if (iDstBpp == 16)
			{
				/* 565 */
				dwRed   = dwRed >> 3;
				dwGreen = dwGreen >> 2;
				dwBlue  = dwBlue >> 3;
				dwColor = (dwRed << 11) | (dwGreen << 5) | (dwBlue);
				*pwDstDatas16bpp = dwColor;
				pwDstDatas16bpp++;
			}
		}
	}
	return 0;
}

5.缩放–图像处理

简单介绍一下近邻取样插值缩放法。
巧的是LCD分辨率是800480,摄像头采集的图片分辨率是640480,两者的宽是一样的,实际上并没有用到缩放。
缩放的原理还是比较简单,图片 某个像素的长/宽 与 图片的长/宽 比值是始终不变的,根据这一规则,可以得到坐标的两个关系:
在这里插入图片描述因此,已知缩放后图片中的任意一点(Dx, Dy),可以求得其对应的原图片中的点Sx=DxSw/Dw,Sy=DySh/Dh,然后直接复制对应原图图像数据到对应的缩放后的图片位置。
此外,为了避免每行重复计算,先将Sx=Dx*Sw/Dw的计算结果保存下来,在每行的处理里直接调用。

int PicZoom(PT_PixelDatas ptOriginPic,PT_PixelDatas ptZoomPic)
{
	unsigned long dwDstWidth = ptZoomPic->iWidth;
	unsigned long *pdwSrcXTable = malloc(sizeof(unsigned long) * dwDstWidth);
	unsigned long x;
	unsigned long y;
	unsigned long dwSrcY;
	unsigned char *pucDest;
	unsigned char *pucSrc;
	unsigned long dwPixelBytes = ptOriginPic->iBpp / 8;

	if(ptOriginPic->iBpp != ptZoomPic->iBpp)
	{
		return -1;
	}
	for(x = 0;x<dwDstWidth;x++)
	{
		pdwSrcXTable[x] = (x * ptOriginPic->iWidth / ptZoomPic->iWidth);
	}
	for(y=0;y<ptZoomPic->iHeight;y++)
	{
		dwSrcY = (y * ptOriginPic->iHeight / ptZoomPic->iHeight);
		pucDest = ptZoomPic->aucPixelDatas + y * ptZoomPic->iLineBytes;
		pucSrc = ptOriginPic->aucPixelDatas + dwSrcY * ptOriginPic->iLineBytes;

		for (x=0;x<dwDstWidth;x++)
		{
			/* 原图座标: pdwSrcXTable[x],srcy
             * 缩放座标: x, y
			 */
			memcpy(pucDest+x*dwPixelBytes, pucSrc+pdwSrcXTable[x]*dwPixelBytes, dwPixelBytes);
		}
	}
	free(pdwSrcXTable);
	return 0;
}

6.融合–merge.c

使用pic_merge()函数来实现将图片放在Framebuffer指定位置。
前面得到了经过缩放(图片的宽和LCD的宽一致)的图片数据,知道了这个数据的地址,理论上直接放到Frambuffer的起始地址即可,这样图片会以LCD左上角为基点显示图片,显示出来效果如下图1,此情况理想的效果应该如图2所示;在这里插入图片描述
以图4的极端情况为例,要想图片居中显示,需要(x,y)的坐标,这个简单,用(LCD宽-图片宽)/2得到x,用(LCD高-图片高)/2得到y。
还需要将以(0,0)为起点的图片数据,依次复制到以(x,y)为起点,新地址的偏移就是(x,y)前的全部数据。
计算思想就是:找到屏幕中心点,然后用屏幕分辨率减去缩放后的横轴图像分辨率再除以2就是左边框的x,y与x类似。

*目的是将小图片放入 大图片中去*/
int PicMerge(int iX, int iY, PT_PixelDatas ptSmallPic, PT_PixelDatas ptBigPic)
{
	int i;
	unsigned char *pucSrc;
	unsigned char *pucDst;

	if ((ptSmallPic->iWidth > ptBigPic->iWidth) || 
		(ptSmallPic->iHeight > ptBigPic->iHeight) ||
		(ptSmallPic->iBpp != ptBigPic->iBpp))
	{
		return -1;
	}
	pucSrc = ptSmallPic->aucPixelDatas;
	pucDst = ptBigPic->aucPixelDatas + iY * ptBigPic->iLineBytes + iX * ptBigPic->iBpp / 8;
	for(i=0;i<ptSmallPic->iHeight;i++)
	{
		memcpy(pucDst,pucSrc,ptSmallPic->iLineBytes);
		pucSrc += ptSmallPic->iLineBytes;
		pucDst += ptBigPic->iLineBytes;
	}
	return 0;
}

7.显示图像

图像显示同样是将屏幕看做是对象构造这个结构体

typedef struct DispOpr {
	char *name;              /* 显示模块的名字 */
	int iXres;               /* X分辨率 */
	int iYres;               /* Y分辨率 */
	int iBpp;                /* 一个象素用多少位来表示 */
	int iLineWidth;          /* 一行数据占据多少字节 */
	unsigned char *pucDispMem;   /* 显存地址 */
	int (*DeviceInit)(void);     /* 设备初始化函数 */
	int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor);    /* 把指定座标的象素设为某颜色 */
	int (*CleanScreen)(unsigned int dwBackColor);                    /* 清屏为某颜色 */
	int (*ShowPage)(PT_PixelDatas ptPixelDatas);                         /* 显示一页,数据源自ptVideoMem */
	struct DispOpr *ptNext;      /* 链表 */
}T_DispOpr, *PT_DispOpr;

7.1管理层–disp_manager.c

还是用链表的方式管理图像显示模块,这里的图像显示模块就一个LCD。
除了常规的注册、显示、获取ops的函数,还有选中指定显示模块并初始化select_and_init_disp_dev(),获取显示设备的参数get_disp_resolution(),获取显示设备的buf信息get_video_buf_for_disp(),以及LCD显示flush_pixel_datas_to_dev()。

7.2 对象–fb.c

fb.c里填充disp_operations结构体的四个操作函数。

  • FBDeviceInit()里通过ioctl()和mmap()得到LCD的可变参数和映射地址,保存到disp_operations结构体里;

  • FBShowPixel()用来显示一个像素,根据BPP不同,对传入的颜色进行对应处理,放在基地址后的坐标偏移;

  • FBCleanScreen()用于全屏显示一种颜色,用于清屏;

  • FBShowPage()用于显示整屏图像,即将数据复制到显存位置;

8.main函数

流程图如下:
在这里插入图片描述

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <config.h>
#include <disp_manager.h>
#include <video_manager.h>
#include <convert_manager.h>
#include <render.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>



/* video2lcd </dev/video0,1,...> */
int main(int argc, char **argv)
{
	int iError;
	T_VideoDevice tVideoDevice;
	PT_VideoConvert ptVideoConvert;
	int iPixelFormatOfVideo;
	int iPixelFormatOfDisp;

	PT_VideoBuf ptVideoBufCur;
	T_VideoBuf tVideoBuf;
	T_VideoBuf tConvertBuf;
	T_VideoBuf tZoomBuf;
	T_VideoBuf tFrameBuf;

	int iLcdWidth;
    int iLcdHeigt;
    int iLcdBpp;

    int iTopLeftX;
    int iTopLeftY;

	float k;

	if(argc != 2)
	{
		printf("Usage :\n");
		printf("%s </dev/video0,1...>\n",argv[0]);
		return -1;
	}
	

	/*一系列初始化*/
	/*注册显示设备*/
	iError = DisplayInit();
	if (iError)
    {
        DBG_PRINTF("VideoInit for %s error!\n", argv[1]);
    }

	/*可能可支持多个显示设备:选择和初始化制定的显示设备*/
	SelectAndInitDefaultDispDev("fb");
	
	iError = GetDispResolution(&iLcdWidth,&iLcdHeigt,&iLcdBpp);
	if (iError != 0)
    {
        DBG_PRINTF("GetDispResolution for %s error!\n", argv[1]);
        
    }
	DBG_PRINTF("GetDispResolution ok!\n");
	iError = GetVideoBufForDisplay(&tFrameBuf);
	if (iError != 0)
    {
        DBG_PRINTF("GetDispResolution for %s error!\n", argv[1]);
    }
	DBG_PRINTF("GetVideoBufForDisplay ok!\n");
	iPixelFormatOfDisp = tFrameBuf.iPixelFormat;

	iError = VideoInit();
	if (iError != 0)
    {
        DBG_PRINTF("VideoInit for %s error!\n", argv[1]);

    }
	DBG_PRINTF("VideoInit ok!\n");
	iError = VideoDeviceInit(argv[1],&tVideoDevice);
	if (iError != 0)
    {
        DBG_PRINTF("VideoDeviceInit for %s error!\n", argv[1]);
        return -1;
    }
	DBG_PRINTF("VideoDeviceInit ok!\n");

	DBG_PRINTF("iPixelFormatOfVideo start!\n");
	iPixelFormatOfVideo = tVideoDevice.ptOpr->GetFormat(&tVideoDevice);
	DBG_PRINTF("iPixelFormatOfVideo %d\n",iPixelFormatOfVideo);

	iError = VideoConvertInit();
	if (iError)
    {
        DBG_PRINTF("VideoConvertInit for %s error!\n", argv[1]);
        return -1;
    }
	DBG_PRINTF("VideoDeviceInit ok!\n");
	ptVideoConvert = GetVideoConvertForFormats(iPixelFormatOfVideo, iPixelFormatOfDisp);
	if (NULL == ptVideoConvert)
    {
        DBG_PRINTF("can not support this format convert\n");
        return -1;
    }
	DBG_PRINTF("GetVideoConvertForFormats ok!\n");
	/*启动摄像头*/
	iError = tVideoDevice.ptOpr->StartDevice(&tVideoDevice);
    if (iError != 0)
    {
        DBG_PRINTF("StartDevice for %s error!\n", argv[1]);
        return -1;
    }
	DBG_PRINTF("StartDevice ok!\n");

	memset(&tVideoBuf, 0, sizeof(tVideoBuf));
    memset(&tConvertBuf, 0, sizeof(tConvertBuf));
	tConvertBuf.iPixelFormat     = iPixelFormatOfDisp;
	tConvertBuf.tPixelDatas.iBpp = iLcdBpp;
    
    memset(&tZoomBuf, 0, sizeof(tZoomBuf));
	DBG_PRINTF("chushihua ok!\n");
	while (1)
	{
		/*读入摄像头数据*/
		iError = tVideoDevice.ptOpr->GetFrame(&tVideoDevice,&tVideoBuf);
		if (iError)
        {
            DBG_PRINTF("GetFrame for %s error!\n", argv[1]);
            return -1;
        }
		DBG_PRINTF("GetFrame ok!\n");
        ptVideoBufCur = &tVideoBuf;

		if (iPixelFormatOfVideo != iPixelFormatOfDisp)
        {
            /* 转换为RGB */
            iError = ptVideoConvert->Convert(&tVideoBuf, &tConvertBuf);
            if (iError != 0)
            {
                DBG_PRINTF("Convert for %s error!\n", argv[1]);
                return -1;
            }
			DBG_PRINTF("Convert ok!\n");
            ptVideoBufCur = &tConvertBuf;
        }

		/*如果图像分辨率大于LCD,缩放*/
		if(ptVideoBufCur->tPixelDatas.iWidth>iLcdWidth || ptVideoBufCur->tPixelDatas.iHeight > iLcdHeigt)
		{
			/* 确定缩放后的分辨率 */
            /* 把图片按比例缩放到VideoMem上, 居中显示
             * 1. 先算出缩放后的大小
             */
			
            k = (float)ptVideoBufCur->tPixelDatas.iHeight / ptVideoBufCur->tPixelDatas.iWidth;
            tZoomBuf.tPixelDatas.iWidth  = iLcdWidth;
            tZoomBuf.tPixelDatas.iHeight = iLcdWidth * k;
            if ( tZoomBuf.tPixelDatas.iHeight > iLcdHeigt)
            {
                tZoomBuf.tPixelDatas.iWidth  = iLcdHeigt / k;
                tZoomBuf.tPixelDatas.iHeight = iLcdHeigt;
            }
            tZoomBuf.tPixelDatas.iBpp        = iLcdBpp;
            tZoomBuf.tPixelDatas.iLineBytes  = tZoomBuf.tPixelDatas.iWidth * tZoomBuf.tPixelDatas.iBpp / 8;
            tZoomBuf.tPixelDatas.iTotalBytes = tZoomBuf.tPixelDatas.iLineBytes * tZoomBuf.tPixelDatas.iHeight;

            if (!tZoomBuf.tPixelDatas.aucPixelDatas)
            {
                tZoomBuf.tPixelDatas.aucPixelDatas = malloc(tZoomBuf.tPixelDatas.iTotalBytes);
            }
            
           	iError =  PicZoom(&ptVideoBufCur->tPixelDatas, &tZoomBuf.tPixelDatas);
			if(iError != 0)
			{
                DBG_PRINTF("PicZoom for %s error!\n", argv[1]);
                return -1;
			}
			DBG_PRINTF("PicZoom ok!\n");
            ptVideoBufCur = &tZoomBuf;

			
		}
		/*将缩放后的数据合并进FrameBuffer里面*/
		/*接着短促居中显示时左上角角标*/
		iTopLeftX = (iLcdWidth - ptVideoBufCur->tPixelDatas.iWidth) / 2;
		iTopLeftY = (iLcdHeigt - ptVideoBufCur->tPixelDatas.iHeight) /2;
		iError =  PicMerge(iTopLeftX, iTopLeftY,&ptVideoBufCur->tPixelDatas, &tFrameBuf.tPixelDatas);
		if(iError != 0)
		{
			DBG_PRINTF("PicZoom for %s error!\n", argv[1]);
			return -1;
		}
		DBG_PRINTF("PicMerge ok!\n");
		/* 把framebuffer的数据刷到LCD上, 显示 */
		FlushPixelDatasToDev(&tFrameBuf.tPixelDatas);
		DBG_PRINTF("FlushPixelDatasToDev ok!\n");
		iError = tVideoDevice.ptOpr->PutFrame(&tVideoDevice, &tVideoBuf);
		if (iError)
		{
			DBG_PRINTF("PutFrame for %s error!\n", argv[1]);
			return -1;
		}
		DBG_PRINTF("PutFrame ok!\n");
	}
	
	return 0;
}


9.Makefile分析

9.1基础知识储备

  • 常用通配符
%.o  ——> 表示所有的.o文件
%.c  ——> 表示所有的.c文件
$@   ——> 表示目标
$<   ——> 表示第1个依赖文件
$^   ——> 表示所有依赖文件
  • 常用变量
:=   ——> 即时变量,它的值在定义的时候确定;(可追加内容)
=    ——> 延时变量,只有在使用到的时候才确定,在定义/等于时并没有确定下来;
?=   ——> 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略;(不覆盖前面的定义)
+=   ——> 附加, 它是即时变量还是延时变量取决于前面的定义;
  • 常用参数
-Wp,-MD,xx.o.d  ——> 生成依赖xx.o.d
-I /xx          ——> 指定头文件(.h)目录xx
-L /xx          ——> 指定库文件(.so)目录xx
-Wall           ——> 打开gcc的所有警告
-Werror         ——> 将所有的警告当成错误进行处理
-O2             ——> 优化等级
-g              ——> gdb调试
  • 常用函数
$(foreach var,list,text)                ——> 将list里面的每个成员,都作text处理
$(filter pattern...,text)               ——> 在text中取出符合patten格式的值
$(filter-out pattern...,text)           ——> 在text中取出不符合patten格式的值
$(wildcard pattern)                     ——> pattern定义了文件名的格式,wildcard取出其中存在的文件
$(patsubst pattern,replacement,$(var))  ——> 从列表中取出每一个值,如果符合pattern,则替换为replacement

例子:

A = a b c 
B = $(foreach f, $(A), $(f).o)
C = a b c d/
D = $(filter %/, $(C))
E = $(filter-out %/, $(C))
files = $(wildcard *.c)
files2 = a.c b.c c.c d.c e.c  abc
files3 = $(wildcard $(files2))
dep_files = $(patsubst %.c,%.d,$(files))
all:
	@echo B = $(B)
	@echo D = $(D)
	@echo E = $(E)
	@echo files = $(files)
	@echo files3 = $(files3)
	@echo dep_files = $(dep_files)

执行结果:

B = a.o b.o c.o                     //把A中每个成员加上后缀.o
D = d/                              //取出C中符合搜索条件"/"的成员,常用于取出文件夹
E = a b c                           //取出C中不符合搜索条件"/"的成员,常用于取出非文件夹
files = a.c b.c c.c                 //取出当前路径下的a.c b.c c.c三个文件,常用于得到当前路径的文件
files3 = a.c b.c c.c                //取出当前路径下存在的a.c b.c c.c三个文件,常用于判断文件是否存在
dep_files = a.d b.d c.d d.d e.d abc //替换符合条件".c"的文件为".d",常用于文件后缀的修改

Makefile分析

makefile分为三类:
1.顶层目录下的Makefile
2.顶层目录下Makefile.build
3.各级子目录的Makefile
在这里插入图片描述

  • 1.顶层目录的Makefile
    它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,主要是定义工具链、编译参数、链接参数(即文件中用export导出的各变量);
# 1.定义编译工具简写并声明(以变其它文件可使用)
CROSS_COMPILE = arm-linux-
AS		= $(CROSS_COMPILE)as
CC 		= $(CROSS_COMPILE)gcc
LD 		= $(CROSS_COMPILE)ld
CPP		= $(CC)	-E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm

STRIP	= $(CROSS_COMPILE)strip
OBJCOPY	= $(CROSS_COMPILE)objcopy
OBJDUMP	= $(CROSS_COMPILE)objdump
#导出相应的变量以便其他文件使用
export AS CC LD CPP AR NM
export STRIP OBJCOPY OBJDUMP
# 2.定义编译选项并声明(警告信息、优化等级、gdb调试、指定本程序头文件路径)
CFLAGS 	:= -Wall -Werror -O2 -g
CFLAGS  += -I $(shell pwd)/include
# 3.定义链接选项并声明(数学库、LibJPEG库)
LDFLAGS := -lm -ljpeg

export CFLAGS LDFLAGS
# 4.定义顶层目录路径并声明(shell命令实现)
TOPDIR := $(shell pwd)
export TOPDIR
# 5.程序目标文件
TARGET := video2lcd
# 6.使用"obj-y"表示各个目标文件,即过程中的所有.o文件(包含当前路径文件和当前路径下的文件夹)
obj-y	+= main.o
obj-y	+= display/
obj-y   += convert/
obj-y	+= render/
obj-y	+= video/

# 7. 目标all:
# 7.1在-C指定目录下,执行指定路径下的文件(即在本路径执行Makefile.build)
# 7.2依赖"built-in.o"生成最终的目标文件
all : 
	make -C ./ -f $(TOPDIR)/Makefile.build
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
# 8.目标clean:清除所有的.o文件和目标文件
clean:
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)
# 9.目标distclean:清除所有的.o文件、.d文件(依赖文件)和目标文件
distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)

  • 2.顶层目录的Makefile.build
# 1.定义"PHONY"表示目标(目前包含一个目标:__build) PHONY用作假想目标
PHONY := __build
# 2.定义目标"__build"内容是下面的所有操作
__build:
# 3.定义"obj-y"表示当前路径的目标文件,定义"subdir-y"表示当前路径下目录的目标文件
obj-y :=
subdir-y :=
# 4.包含当前路径的Makefile(为了获取"obj-y"的内容)
include Makefile

#5.提取各级子目录名
# 5.1filter函数从obj-y中筛选出含"/"的内容,即目录
# 5.2patsubst函数将上述结果中的"/"替换为空,subdir-y即为当前路径的目录名(不含"/")
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y := $(patsubst %/, %, $(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)

# 6.把"obj-y"都加上"/built-in.o"后缀
# c/built-in.o d/built-in.o jia houzhui
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

# 7.得到"obj-y"中的非文件夹文件(即各个.o文件)
# a.o b.o  取出目标中的 %/    得到 .a.o.d .b.o.d
cur_objs := $(filter-out %/, $(obj-y))

# 8. 得到依赖文件(.d文件)
# 8.1foreach把前面的*.o文件变为.*.o.d(这是当前目录Makefile提供的数据)
# 8.2wildcard根据这些.d名字在当前路径查找,得到真正存在的.d文件
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
# 9.如果"dep_files"不为空,则包含(即包含了.d依赖文件,保证头文件修改后程序会重新编译)
ifneq ($(dep_files),)
  include $(dep_files)
endif
# 10.新增目标(目前包含两个目标:__build和subdir-y的各个成员)
PHONY += $(subdir-y)
# 11.目标__build依赖于subdir-y各个成员和built-in.o
__build : $(subdir-y) built-in.o
# 12.对subdir-y的每个成员(即目录),都调用Makefile.build
$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build
# 13.built-in.o依赖当前路径下的.o和目录下的built-in.o(即将当前路径下的.o链接成built-in.o)
built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^
# 14.定义dep_file为所有的依赖
dep_file = .$@.d
# 15.所有的.o依赖于所有的.c,编译过程生成对应.d文件
%.o : %.c
	$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
# 16.声明$(PHONY)是个假想目标
.PHONY : $(PHONY)

  • 3.各级子目录的Makefile
    指定当前目录下需要编进程序去的文件;
obj-y += video_manager.o
obj-y += v4l2.o
obj-y += operation/
  • 4.实际编译过程

1.顶层目录执行make,调用顶层目录下的Makefile,调用make -C ./ -f /work/project2/06.video2lcd/01/Makefile.build,执行Makefile.build;
2.Makefile.build里调用make -C $@ -f $(TOPDIR)/Makefile.build对每个目录都执行Makefile.build;
3.以video目录为例,调用Makefile.build,会执行以下操作:
  - 编译每一个.c:
  arm-linux–gcc -Wall -Werror -O2 -g -I /work/drv/code/include -Wp,-MD,.v4l2.o.d -c -o v4l2.o v4l2.c
  - 将所有.o链接成built-in.o:
  arm-linux-ld -r -o built-in.o v4l2.o video_manager.o
4.完成对当前目录的内容编译后,再对当前路径的.c文件编译:
arm-linux-gnueabihf-gcc -Wall -Werror -O2 -g -I /work/drv/code/include -Wp,-MD,.main.o.d -c -o main.o main.c
5.将各子目录生成的built-in.o与main.o链接生成新built-in.o;
6.最后依赖built-in.o输出目标文件arm-linux-gcc -o video2lcd built-in.o -lm -ljpeg

10.总结

对于一个应用,首先写各个子模块,然后将各个子模块的初始化函数调用一遍,然后进行格式的获取,数据的获取、转换、缩放、融合、放进framebuffer中去,就完成了数据的收集、转换和显示

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值