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-(CONFIGFBS3C2410)+=s3c2410fb.oobj(CONFIG_FB_S3C2410) += s3c2410fb.o obj-(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 = [email protected]
# 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中去,就完成了數據的收集、轉換和顯示

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章