对于一个应用程序,最重要的是明白目的是什么:将摄像头的数据解析出来,按一帧一个图片的方式将数据传到LCD的Framebuffer中去(如果LCD没有自动将Framebuffer中的数据刷到LCD上还需要进行flush操作)
将USB的数据传入开发板中内核,所以USB摄像头是插在开发板的USB接口上。
在开发板中的内核,需要加入LCD驱动、背光驱动、UVC驱动。
驱动的使用方法有两种:
安装工具链: 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- ( 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)
使用之前做好的根文件系统
cd /work/nfs_root
sudo tar xjf fs_mini_mdev_new.tar.bz2
sudo chown book:book fs_mini_mdev_new (赋予权限)
启动开发板至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
编程时就是讲每一个实体抽象出一些共性与个性,共性作为公布出来接口,个性作为自己的私有。将共性一个一个串联起来成为一个链表,在上层想要访问该实体时,必须先去管理层寻找这个实体,再从接口进行数据的访问以及读写。
在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;
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;
}
首先分配设置注册一个结构体
/* 构造一个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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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);
}
前面的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;
与video_manager.h构造方式类似
这里有三类转换:MJPEG转RGB、YUV转RGB、RGB转RGB,将它们都放到链表中,通过get_video_convert_format()传入待转换的格式,从链表中依次查询谁支持该转换,如果支持,就得到p_video_convert,就可以调用到对应的操作函数。
解压操作过程如下:
1、分配jpeg对象结构体空间,并初始化
2、指定解压数据源
3、获取解压文件信息
4、为解压设定参数,包括图像大小和颜色空间
5、开始解压缩 6、取数据并显示
7、解压完毕
8、释放资源和退出程序
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;
}
简单介绍一下近邻取样插值缩放法。
巧的是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;xiWidth / ptZoomPic->iWidth);
}
for(y=0;yiHeight;y++)
{
dwSrcY = (y * ptOriginPic->iHeight / ptZoomPic->iHeight);
pucDest = ptZoomPic->aucPixelDatas + y * ptZoomPic->iLineBytes;
pucSrc = ptOriginPic->aucPixelDatas + dwSrcY * ptOriginPic->iLineBytes;
for (x=0;x
使用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;iiHeight;i++)
{
memcpy(pucDst,pucSrc,ptSmallPic->iLineBytes);
pucSrc += ptSmallPic->iLineBytes;
pucDst += ptBigPic->iLineBytes;
}
return 0;
}
图像显示同样是将屏幕看做是对象构造这个结构体
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;
还是用链表的方式管理图像显示模块,这里的图像显示模块就一个LCD。
除了常规的注册、显示、获取ops的函数,还有选中指定显示模块并初始化select_and_init_disp_dev(),获取显示设备的参数get_disp_resolution(),获取显示设备的buf信息get_video_buf_for_disp(),以及LCD显示flush_pixel_datas_to_dev()。
fb.c里填充disp_operations结构体的四个操作函数。
FBDeviceInit()里通过ioctl()和mmap()得到LCD的可变参数和映射地址,保存到disp_operations结构体里;
FBShowPixel()用来显示一个像素,根据BPP不同,对传入的颜色进行对应处理,放在基地址后的坐标偏移;
FBCleanScreen()用于全屏显示一种颜色,用于清屏;
FBShowPage()用于显示整屏图像,即将数据复制到显存位置;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* video2lcd */
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 \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;
}
%.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分为三类:
1.顶层目录下的Makefile
2.顶层目录下Makefile.build
3.各级子目录的Makefile
# 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)
# 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)
obj-y += video_manager.o
obj-y += v4l2.o
obj-y += operation/
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
对于一个应用,首先写各个子模块,然后将各个子模块的初始化函数调用一遍,然后进行格式的获取,数据的获取、转换、缩放、融合、放进framebuffer中去,就完成了数据的收集、转换和显示