對於一個應用程序,最重要的是明白目的是什麼:將攝像頭的數據解析出來,按一幀一個圖片的方式將數據傳到LCD的Framebuffer中去(如果LCD沒有自動將Framebuffer中的數據刷到LCD上還需要進行flush操作)
1.準備工作
將USB的數據傳入開發板中內核,所以USB攝像頭是插在開發板的USB接口上。
在開發板中的內核,需要加入LCD驅動、背光驅動、UVC驅動。
驅動的使用方法有兩種:
- 手動加載:每次上電後進行insmode xx.ko。這種方式適合調式產品時進行。
- 自動加載:將LCD驅動、背光驅動、UVC驅動加載到內核配置中(make menuconfig,找到相應的驅動,在選擇按下y(y表示編譯進內個,m表示編譯成模塊,n表示不變異)),這樣進入系統後就已經有了這三個驅動
1.1安裝工具鏈arm-linux-gcc
-
安裝工具鏈: sudo tar xjf arm-linux-gcc-4.3.2.tar.bz2 -C /
-
設置環境變量: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" -
編譯內核:tar xjf linux-3.4.2.tar.bz2
cd linux-3.4.2 -
方式一:打補丁: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-(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)
- make uImage
- 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.應用流程
- 從攝像頭中獲取video_buf數據,這裏的數據可能有多種分辨率以及多種格式 (YUV,MJPEG,RGB)
- s3c2440開發板僅支持RGB格式的數據,因此需要一個數據格式轉換(YUV2RGB,MJPEG2RGB,RGB2RGB)
- 轉換後的數據由於可能存在多種分辨率,因此需要確定LCD的分辨率(disp方面的函數)以及將數據轉換(縮放的函數)
- 然後將縮放後的數據放到顯存中去
- 最後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、釋放資源和退出程序
- 分配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);
- 指定解壓數據源
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中去,就完成了數據的收集、轉換和顯示