对于一个应用程序,最重要的是明白目的是什么:将摄像头的数据解析出来,按一帧一个图片的方式将数据传到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-(CONFIGF​BS​3C2410)+=s3c2410fb.oobj−(CONFIG_FB_S3C2410) += lcd_4.3.o

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

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

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

1.2最小文件系统的制作

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

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

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

2.应用流程

  1. 从摄像头中获取video_buf数据,这里的数据可能有多种分辨率以及多种格式 (YUV,MJPEG,RGB)
  2. s3c2440开发板仅支持RGB格式的数据,因此需要一个数据格式转换(YUV2RGB,MJPEG2RGB,RGB2RGB)
  3. 转换后的数据由于可能存在多种分辨率,因此需要确定LCD的分辨率(disp方面的函数)以及将数据转换(缩放的函数)
  4. 然后将缩放后的数据放到显存中去
  5. 最后LCD控制器会将显存中的数据自动搬运到LCD上去。

    对于应用编程,编写者一定要有这样的思想:面向对象编程,也就是把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数,在C语言中常常用结构体(struct)来实现。

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

3.获取摄像头数据

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

/*由于相互引用,所以需要申明*/
struct VideoDevice;
struct VideoOpr;
typedef struct VideoDevice T_VideoDevice, *PT_VideoDevice;
typedef struct VideoOpr T_VideoOpr, *PT_VideoOpr;struct VideoDevice {int iFd;       //文件句柄int iPixelFormat;      //像素格式int iWidth;          /分辨率:宽*高int iHeight;int iVideoBufCnt;       //buf数量int iVideoBufMaxLen;     //每个buf最大长度int iVideoBufCurIndex;        //当前buf索引unsigned char *pucVideBuf[NB_BUFFER];     //每个video buf的地址/* 函数 */PT_VideoOpr ptOpr;
};
//摄像头设备的操作函数
struct VideoOpr {char *name;int (*InitDevice)(char *strDevName, PT_VideoDevice ptVideoDevice);int (*ExitDevice)(PT_VideoDevice ptVideoDevice);int (*GetFrame)(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);int (*GetFormat)(PT_VideoDevice ptVideoDevice);int (*PutFrame)(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);int (*StartDevice)(PT_VideoDevice ptVideoDevice);int (*StopDevice)(PT_VideoDevice ptVideoDevice);struct VideoOpr *ptNext;
};
/* 图片的象素数据 */
typedef struct PixelDatas {int iWidth;   /* 宽度: 一行有多少个象素 */int iHeight;  /* 高度: 一列有多少个象素 */int iBpp;     /* 一个象素用多少位来表示 */int iLineBytes;  /* 一行数据有多少字节 */int iTotalBytes; /* 所有字节数 */ unsigned char *aucPixelDatas;  /* 象素数据存储的地方 */
}T_PixelDatas, *PT_PixelDatas;/*摄像头的数据*/
typedef struct VideoBuf {T_PixelDatas tPixelDatas; //图片像素的数据int iPixelFormat;         //像素的格式
}T_VideoBuf, *PT_VideoBuf;

3.1 管理层–video_manager.c

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

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

3.2 摄像头设备–v4l2.c

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

/* 构造一个VideoOpr结构体 */
static T_VideoOpr g_tV4l2VideoOpr = {.name        = "v4l2",.InitDevice  = V4l2InitDevice,.ExitDevice  = V4l2ExitDevice,.GetFormat   = V4l2GetFormat,.GetFrame    = V4l2GetFrameForStreaming,.PutFrame    = V4l2PutFrameForStreaming,.StartDevice = V4l2StartDevice,.StopDevice  = V4l2StopDevice,
};/* 注册这个结构体 */
int V4l2Init(void)
{return RegisterVideoOpr(&g_tV4l2VideoOpr);
}

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

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

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

对于streaming接口,使用v4l2_get_frame_readwrite()来获取数据。

#include <config.h>
#include <video_manager.h>
#include <disp_manager.h>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>static int g_aiSupportedFormats[] = {V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_RGB565};static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static T_VideoOpr g_tV4l2VideoOpr;static int isSupportThisFormat(int iPixelFormat)
{int i;for (i = 0; i < sizeof(g_aiSupportedFormats)/sizeof(g_aiSupportedFormats[0]); i++){if (g_aiSupportedFormats[i] == iPixelFormat)return 1;}return 0;
}/* 参考 luvcview *//* open* VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)* VIDIOC_ENUM_FMT 查询支持哪种格式* VIDIOC_S_FMT    设置摄像头使用哪种格式* VIDIOC_REQBUFS  申请buffer对于 streaming:* VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap* VIDIOC_QBUF     放入队列* VIDIOC_STREAMON 启动设备* poll            等待有数据* VIDIOC_DQBUF    从队列中取出* 处理....* VIDIOC_QBUF     放入队列* ....对于read,write:read处理....read* VIDIOC_STREAMOFF 停止设备**/static int V4l2InitDevice(char *strDevName, PT_VideoDevice ptVideoDevice)
{int i;int iFd;int iError;struct v4l2_capability tV4l2Cap;struct v4l2_fmtdesc tFmtDesc;struct v4l2_format  tV4l2Fmt;struct v4l2_requestbuffers tV4l2ReqBuffs;struct v4l2_buffer tV4l2Buf;int iLcdWidth;int iLcdHeigt;int iLcdBpp;iFd = open(strDevName, O_RDWR);if (iFd < 0){DBG_PRINTF("can not open %s\n", strDevName);return -1;}ptVideoDevice->iFd = iFd;iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);if (iError) {DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);goto err_exit;}if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){DBG_PRINTF("%s is not a video capture device\n", strDevName);goto err_exit;}if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {DBG_PRINTF("%s supports streaming i/o\n", strDevName);}if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {DBG_PRINTF("%s supports read i/o\n", strDevName);}memset(&tFmtDesc, 0, sizeof(tFmtDesc));tFmtDesc.index = 0;tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {if (isSupportThisFormat(tFmtDesc.pixelformat)){ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;break;}tFmtDesc.index++;}if (!ptVideoDevice->iPixelFormat){DBG_PRINTF("can not support the format of this device\n");goto err_exit;        }/* set format in */GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;tV4l2Fmt.fmt.pix.width       = iLcdWidth;tV4l2Fmt.fmt.pix.height      = iLcdHeigt;tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;/* 如果驱动程序发现无法某些参数(比如分辨率),* 它会调整这些参数, 并且返回给应用程序*/iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); if (iError) {DBG_PRINTF("Unable to set format\n");goto err_exit;        }ptVideoDevice->iWidth  = tV4l2Fmt.fmt.pix.width;ptVideoDevice->iHeight = tV4l2Fmt.fmt.pix.height;/* request buffers */memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));tV4l2ReqBuffs.count = NB_BUFFER;tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);if (iError) {DBG_PRINTF("Unable to allocate buffers.\n");goto err_exit;        }ptVideoDevice->iVideoBufCnt = tV4l2ReqBuffs.count;if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING){/* map the buffers */for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) {memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.index = i;tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);if (iError) {DBG_PRINTF("Unable to query buffer.\n");goto err_exit;}ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,tV4l2Buf.m.offset);if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED) {DBG_PRINTF("Unable to map buffer\n");goto err_exit;}}        /* Queue the buffers. */for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) {memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.index = i;tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);if (iError){DBG_PRINTF("Unable to queue buffer.\n");goto err_exit;}}}else if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE){g_tV4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;g_tV4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;/* read(fd, buf, size) */ptVideoDevice->iVideoBufCnt  = 1;/* 在这个程序所能支持的格式里, 一个象素最多只需要4字节 */ptVideoDevice->iVideoBufMaxLen = ptVideoDevice->iWidth * ptVideoDevice->iHeight * 4;ptVideoDevice->pucVideBuf[0] = malloc(ptVideoDevice->iVideoBufMaxLen);}ptVideoDevice->ptOpr = &g_tV4l2VideoOpr;return 0;err_exit:    close(iFd);return -1;
}static int V4l2ExitDevice(PT_VideoDevice ptVideoDevice)
{int i;for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++){if (ptVideoDevice->pucVideBuf[i]){munmap(ptVideoDevice->pucVideBuf[i], ptVideoDevice->iVideoBufMaxLen);ptVideoDevice->pucVideBuf[i] = NULL;}}close(ptVideoDevice->iFd);return 0;
}static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{struct pollfd tFds[1];int iRet;struct v4l2_buffer tV4l2Buf;/* poll */tFds[0].fd     = ptVideoDevice->iFd;tFds[0].events = POLLIN;iRet = poll(tFds, 1, -1);if (iRet <= 0){DBG_PRINTF("poll error!\n");return -1;}/* VIDIOC_DQBUF */memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);if (iRet < 0) {DBG_PRINTF("Unable to dequeue buffer.\n");return -1;}ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index;ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 :  \0;ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;ptVideoBuf->tPixelDatas.iTotalBytes   = tV4l2Buf.bytesused;ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index];    return 0;
}static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{/* VIDIOC_QBUF */struct v4l2_buffer tV4l2Buf;int iError;memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.index  = ptVideoDevice->iVideoBufCurIndex;tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf);if (iError) {DBG_PRINTF("Unable to queue buffer.\n");return -1;}return 0;
}static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{int iRet;iRet = read(ptVideoDevice->iFd, ptVideoDevice->pucVideBuf[0], ptVideoDevice->iVideoBufMaxLen);if (iRet <= 0){return -1;}ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565)? 16 : \0;ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;ptVideoBuf->tPixelDatas.iTotalBytes   = iRet;ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[0];    return 0;
}static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{return 0;
}static int V4l2StartDevice(PT_VideoDevice ptVideoDevice)
{int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;int iError;iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);if (iError) {DBG_PRINTF("Unable to start capture.\n");return -1;}return 0;
}static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
{int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;int iError;iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMOFF, &iType);if (iError) {DBG_PRINTF("Unable to stop capture.\n");return -1;}return 0;
}static int V4l2GetFormat(PT_VideoDevice ptVideoDevice)
{return ptVideoDevice->iPixelFormat;
}/* 构造一个VideoOpr结构体 */
static T_VideoOpr g_tV4l2VideoOpr = {.name        = "v4l2",.InitDevice  = V4l2InitDevice,.ExitDevice  = V4l2ExitDevice,.GetFormat   = V4l2GetFormat,.GetFrame    = V4l2GetFrameForStreaming,.PutFrame    = V4l2PutFrameForStreaming,.StartDevice = V4l2StartDevice,.StopDevice  = V4l2StopDevice,
};/* 注册这个结构体 */
int V4l2Init(void)
{return RegisterVideoOpr(&g_tV4l2VideoOpr);
}

4.格式转换

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

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

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

4.1 管理层–convert_manager.c

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

4.2 对象1–mjpeg2rgb.c

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

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

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

tDInfo.err = jpeg_std_error(&tJErr);

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

初始化cinfo结构体。

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

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

iRet = jpeg_read_header(&tDInfo, TRUE);

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

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

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

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


tDInfo.out_color_space=JCS_GRAYSCALE;  

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

jpeg_start_decompress(&tDInfo);

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

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

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

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

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

// 循环调用jpeg_read_scanlines来一行一行地获得解压的数据while (tDInfo.output_scanline < tDInfo.output_height) {/* 得到一行数据,里面的颜色格式为0xRR, 0xGG, 0xBB */(void) jpeg_read_scanlines(&tDInfo, &aucLineBuffer, 1);// 转到ptPixelDatas去CovertOneLine(ptPixelDatas->iWidth, 24, ptPixelDatas->iBpp, aucLineBuffer, pucDest);pucDest += ptPixelDatas->iLineBytes;}

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

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

static void MyErrorExit(j_common_ptr ptCInfo)
{static char errStr[JMSG_LENGTH_MAX];PT_MyErrorMgr ptMyErr = (PT_MyErrorMgr)ptCInfo->err;/* Create the message */(*ptCInfo->err->format_message) (ptCInfo, errStr);DBG_PRINTF("%s\n", errStr);longjmp(ptMyErr->setjmp_buffer, 1);
}

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

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

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

5.缩放–图像处理

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

int PicZoom(PT_PixelDatas ptOriginPic,PT_PixelDatas ptZoomPic)
{unsigned long dwDstWidth = ptZoomPic->iWidth;unsigned long *pdwSrcXTable = malloc(sizeof(unsigned long) * dwDstWidth);unsigned long x;unsigned long y;unsigned long dwSrcY;unsigned char *pucDest;unsigned char *pucSrc;unsigned long dwPixelBytes = ptOriginPic->iBpp / 8;if(ptOriginPic->iBpp != ptZoomPic->iBpp){return -1;}for(x = 0;x<dwDstWidth;x++){pdwSrcXTable[x] = (x * ptOriginPic->iWidth / ptZoomPic->iWidth);}for(y=0;y<ptZoomPic->iHeight;y++){dwSrcY = (y * ptOriginPic->iHeight / ptZoomPic->iHeight);pucDest = ptZoomPic->aucPixelDatas + y * ptZoomPic->iLineBytes;pucSrc = ptOriginPic->aucPixelDatas + dwSrcY * ptOriginPic->iLineBytes;for (x=0;x<dwDstWidth;x++){/* 原图座标: pdwSrcXTable[x],srcy* 缩放座标: x, y*/memcpy(pucDest+x*dwPixelBytes, pucSrc+pdwSrcXTable[x]*dwPixelBytes, dwPixelBytes);}}free(pdwSrcXTable);return 0;
}

6.融合–merge.c

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

*目的是将小图片放入 大图片中去*/
int PicMerge(int iX, int iY, PT_PixelDatas ptSmallPic, PT_PixelDatas ptBigPic)
{int i;unsigned char *pucSrc;unsigned char *pucDst;if ((ptSmallPic->iWidth > ptBigPic->iWidth) || (ptSmallPic->iHeight > ptBigPic->iHeight) ||(ptSmallPic->iBpp != ptBigPic->iBpp)){return -1;}pucSrc = ptSmallPic->aucPixelDatas;pucDst = ptBigPic->aucPixelDatas + iY * ptBigPic->iLineBytes + iX * ptBigPic->iBpp / 8;for(i=0;i<ptSmallPic->iHeight;i++){memcpy(pucDst,pucSrc,ptSmallPic->iLineBytes);pucSrc += ptSmallPic->iLineBytes;pucDst += ptBigPic->iLineBytes;}return 0;
}

7.显示图像

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

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

7.1管理层–disp_manager.c

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

7.2 对象–fb.c

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

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

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

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

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

8.main函数

流程图如下:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <config.h>
#include <disp_manager.h>
#include <video_manager.h>
#include <convert_manager.h>
#include <render.h>
#include <string.h>#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>/* video2lcd </dev/video0,1,...> */
int main(int argc, char **argv)
{int iError;T_VideoDevice tVideoDevice;PT_VideoConvert ptVideoConvert;int iPixelFormatOfVideo;int iPixelFormatOfDisp;PT_VideoBuf ptVideoBufCur;T_VideoBuf tVideoBuf;T_VideoBuf tConvertBuf;T_VideoBuf tZoomBuf;T_VideoBuf tFrameBuf;int iLcdWidth;int iLcdHeigt;int iLcdBpp;int iTopLeftX;int iTopLeftY;float k;if(argc != 2){printf("Usage :\n");printf("%s </dev/video0,1...>\n",argv[0]);return -1;}/*一系列初始化*//*注册显示设备*/iError = DisplayInit();if (iError){DBG_PRINTF("VideoInit for %s error!\n", argv[1]);}/*可能可支持多个显示设备:选择和初始化制定的显示设备*/SelectAndInitDefaultDispDev("fb");iError = GetDispResolution(&iLcdWidth,&iLcdHeigt,&iLcdBpp);if (iError != 0){DBG_PRINTF("GetDispResolution for %s error!\n", argv[1]);}DBG_PRINTF("GetDispResolution ok!\n");iError = GetVideoBufForDisplay(&tFrameBuf);if (iError != 0){DBG_PRINTF("GetDispResolution for %s error!\n", argv[1]);}DBG_PRINTF("GetVideoBufForDisplay ok!\n");iPixelFormatOfDisp = tFrameBuf.iPixelFormat;iError = VideoInit();if (iError != 0){DBG_PRINTF("VideoInit for %s error!\n", argv[1]);}DBG_PRINTF("VideoInit ok!\n");iError = VideoDeviceInit(argv[1],&tVideoDevice);if (iError != 0){DBG_PRINTF("VideoDeviceInit for %s error!\n", argv[1]);return -1;}DBG_PRINTF("VideoDeviceInit ok!\n");DBG_PRINTF("iPixelFormatOfVideo start!\n");iPixelFormatOfVideo = tVideoDevice.ptOpr->GetFormat(&tVideoDevice);DBG_PRINTF("iPixelFormatOfVideo %d\n",iPixelFormatOfVideo);iError = VideoConvertInit();if (iError){DBG_PRINTF("VideoConvertInit for %s error!\n", argv[1]);return -1;}DBG_PRINTF("VideoDeviceInit ok!\n");ptVideoConvert = GetVideoConvertForFormats(iPixelFormatOfVideo, iPixelFormatOfDisp);if (NULL == ptVideoConvert){DBG_PRINTF("can not support this format convert\n");return -1;}DBG_PRINTF("GetVideoConvertForFormats ok!\n");/*启动摄像头*/iError = tVideoDevice.ptOpr->StartDevice(&tVideoDevice);if (iError != 0){DBG_PRINTF("StartDevice for %s error!\n", argv[1]);return -1;}DBG_PRINTF("StartDevice ok!\n");memset(&tVideoBuf, 0, sizeof(tVideoBuf));memset(&tConvertBuf, 0, sizeof(tConvertBuf));tConvertBuf.iPixelFormat     = iPixelFormatOfDisp;tConvertBuf.tPixelDatas.iBpp = iLcdBpp;memset(&tZoomBuf, 0, sizeof(tZoomBuf));DBG_PRINTF("chushihua ok!\n");while (1){/*读入摄像头数据*/iError = tVideoDevice.ptOpr->GetFrame(&tVideoDevice,&tVideoBuf);if (iError){DBG_PRINTF("GetFrame for %s error!\n", argv[1]);return -1;}DBG_PRINTF("GetFrame ok!\n");ptVideoBufCur = &tVideoBuf;if (iPixelFormatOfVideo != iPixelFormatOfDisp){/* 转换为RGB */iError = ptVideoConvert->Convert(&tVideoBuf, &tConvertBuf);if (iError != 0){DBG_PRINTF("Convert for %s error!\n", argv[1]);return -1;}DBG_PRINTF("Convert ok!\n");ptVideoBufCur = &tConvertBuf;}/*如果图像分辨率大于LCD,缩放*/if(ptVideoBufCur->tPixelDatas.iWidth>iLcdWidth || ptVideoBufCur->tPixelDatas.iHeight > iLcdHeigt){/* 确定缩放后的分辨率 *//* 把图片按比例缩放到VideoMem上, 居中显示* 1. 先算出缩放后的大小*/k = (float)ptVideoBufCur->tPixelDatas.iHeight / ptVideoBufCur->tPixelDatas.iWidth;tZoomBuf.tPixelDatas.iWidth  = iLcdWidth;tZoomBuf.tPixelDatas.iHeight = iLcdWidth * k;if ( tZoomBuf.tPixelDatas.iHeight > iLcdHeigt){tZoomBuf.tPixelDatas.iWidth  = iLcdHeigt / k;tZoomBuf.tPixelDatas.iHeight = iLcdHeigt;}tZoomBuf.tPixelDatas.iBpp        = iLcdBpp;tZoomBuf.tPixelDatas.iLineBytes  = tZoomBuf.tPixelDatas.iWidth * tZoomBuf.tPixelDatas.iBpp / 8;tZoomBuf.tPixelDatas.iTotalBytes = tZoomBuf.tPixelDatas.iLineBytes * tZoomBuf.tPixelDatas.iHeight;if (!tZoomBuf.tPixelDatas.aucPixelDatas){tZoomBuf.tPixelDatas.aucPixelDatas = malloc(tZoomBuf.tPixelDatas.iTotalBytes);}iError =  PicZoom(&ptVideoBufCur->tPixelDatas, &tZoomBuf.tPixelDatas);if(iError != 0){DBG_PRINTF("PicZoom for %s error!\n", argv[1]);return -1;}DBG_PRINTF("PicZoom ok!\n");ptVideoBufCur = &tZoomBuf;}/*将缩放后的数据合并进FrameBuffer里面*//*接着短促居中显示时左上角角标*/iTopLeftX = (iLcdWidth - ptVideoBufCur->tPixelDatas.iWidth) / 2;iTopLeftY = (iLcdHeigt - ptVideoBufCur->tPixelDatas.iHeight) /2;iError =  PicMerge(iTopLeftX, iTopLeftY,&ptVideoBufCur->tPixelDatas, &tFrameBuf.tPixelDatas);if(iError != 0){DBG_PRINTF("PicZoom for %s error!\n", argv[1]);return -1;}DBG_PRINTF("PicMerge ok!\n");/* 把framebuffer的数据刷到LCD上, 显示 */FlushPixelDatasToDev(&tFrameBuf.tPixelDatas);DBG_PRINTF("FlushPixelDatasToDev ok!\n");iError = tVideoDevice.ptOpr->PutFrame(&tVideoDevice, &tVideoBuf);if (iError){DBG_PRINTF("PutFrame for %s error!\n", argv[1]);return -1;}DBG_PRINTF("PutFrame ok!\n");}return 0;
}

9.Makefile分析

9.1基础知识储备

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

例子:

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

执行结果:

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

Makefile分析

makefile分为三类:
1.顶层目录下的Makefile
2.顶层目录下Makefile.build
3.各级子目录的Makefile

  • 1.顶层目录的Makefile
    它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,主要是定义工具链、编译参数、链接参数(即文件中用export导出的各变量);
# 1.定义编译工具简写并声明(以变其它文件可使用)
CROSS_COMPILE = arm-linux-
AS      = $(CROSS_COMPILE)as
CC      = $(CROSS_COMPILE)gcc
LD      = $(CROSS_COMPILE)ld
CPP     = $(CC)    -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nmSTRIP  = $(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 -ljpegexport CFLAGS LDFLAGS
# 4.定义顶层目录路径并声明(shell命令实现)
TOPDIR := $(shell pwd)
export TOPDIR
# 5.程序目标文件
TARGET := video2lcd
# 6.使用"obj-y"表示各个目标文件,即过程中的所有.o文件(包含当前路径文件和当前路径下的文件夹)
obj-y   += main.o
obj-y   += display/
obj-y   += convert/
obj-y   += render/
obj-y   += video/# 7. 目标all:
# 7.1在-C指定目录下,执行指定路径下的文件(即在本路径执行Makefile.build)
# 7.2依赖"built-in.o"生成最终的目标文件
all : make -C ./ -f $(TOPDIR)/Makefile.build$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
# 8.目标clean:清除所有的.o文件和目标文件
clean:rm -f $(shell find -name "*.o")rm -f $(TARGET)
# 9.目标distclean:清除所有的.o文件、.d文件(依赖文件)和目标文件
distclean:rm -f $(shell find -name "*.o")rm -f $(shell find -name "*.d")rm -f $(TARGET)
  • 2.顶层目录的Makefile.build
# 1.定义"PHONY"表示目标(目前包含一个目标:__build) PHONY用作假想目标
PHONY := __build
# 2.定义目标"__build"内容是下面的所有操作
__build:
# 3.定义"obj-y"表示当前路径的目标文件,定义"subdir-y"表示当前路径下目录的目标文件
obj-y :=
subdir-y :=
# 4.包含当前路径的Makefile(为了获取"obj-y"的内容)
include Makefile#5.提取各级子目录名
# 5.1filter函数从obj-y中筛选出含"/"的内容,即目录
# 5.2patsubst函数将上述结果中的"/"替换为空,subdir-y即为当前路径的目录名(不含"/")
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y := $(patsubst %/, %, $(filter %/, $(obj-y)))
subdir-y    += $(__subdir-y)# 6.把"obj-y"都加上"/built-in.o"后缀
# c/built-in.o d/built-in.o jia houzhui
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)# 7.得到"obj-y"中的非文件夹文件(即各个.o文件)
# a.o b.o  取出目标中的 %/    得到 .a.o.d .b.o.d
cur_objs := $(filter-out %/, $(obj-y))# 8. 得到依赖文件(.d文件)
# 8.1foreach把前面的*.o文件变为.*.o.d(这是当前目录Makefile提供的数据)
# 8.2wildcard根据这些.d名字在当前路径查找,得到真正存在的.d文件
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
# 9.如果"dep_files"不为空,则包含(即包含了.d依赖文件,保证头文件修改后程序会重新编译)
ifneq ($(dep_files),)include $(dep_files)
endif
# 10.新增目标(目前包含两个目标:__build和subdir-y的各个成员)
PHONY += $(subdir-y)
# 11.目标__build依赖于subdir-y各个成员和built-in.o
__build : $(subdir-y) built-in.o
# 12.对subdir-y的每个成员(即目录),都调用Makefile.build
$(subdir-y):make -C $@ -f $(TOPDIR)/Makefile.build
# 13.built-in.o依赖当前路径下的.o和目录下的built-in.o(即将当前路径下的.o链接成built-in.o)
built-in.o : $(cur_objs) $(subdir_objs)$(LD) -r -o $@ $^
# 14.定义dep_file为所有的依赖
dep_file = .$@.d
# 15.所有的.o依赖于所有的.c,编译过程生成对应.d文件
%.o : %.c$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
# 16.声明$(PHONY)是个假想目标
.PHONY : $(PHONY)
  • 3.各级子目录的Makefile
    指定当前目录下需要编进程序去的文件;
obj-y += video_manager.o
obj-y += v4l2.o
obj-y += operation/
  • 4.实际编译过程

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

10.总结

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

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

  1. 第三阶段应用层——2.4 视频监控—从0写USB摄像头驱动(1)-描述符的分析与打印

    视频监控-从0写USB摄像头驱动(1)-描述符的分析与打印 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3) 软件平台:运行于VMware Workstation 12 Player下U ...

  2. linux cmos摄像头,Linux摄像头驱动4——CMOS摄像头

    Linux摄像头驱动学习第四篇,对CMOS摄像头进行学习,实现在Tiny4412上使用CMOS摄像头采集图像,在LCD上显示图像. 坚持到了Linux摄像头学习的最后一部分--CMOS摄像头. 写完本 ...

  3. Air系列模块驱动lcd显示

    特别说明:只适用于有spi接口的模块,推荐使用Air202和Air800 1.硬件连线:选择标准SPI协议,对应的LCD与模块之间连线为: Air模块 LCD GND------- 地(GND) SP ...

  4. 飞思卡尔比赛K60驱动OLED12864显示摄像头采集的赛道图像,完整代码分享

    一.首先采集摄像头图像,由于硬件不同采集方式也不一样,我就不多做说明 二.将采集到的图像进行二值化 三.下面为完整显示函数 备注:大家主需要修改对应的引脚就行(修改初始化和宏定义) led.c文件 # ...

  5. JZ2440第三期LCD显示摄像头数据3th

    convert_manager.h文件: #ifndef _CONVERT_MANAGER_H #define _CONVERT_MANAGER_H#include <config.h> ...

  6. w ndows摄像头驱动怎么安,一步一步教你安装如何摄像头驱动(图)

    1,摄像头驱动手动安装 将摄像头连接到电脑有效的USB接口上,系统将会提示找到新硬件,并弹出产品安装向导: 教你安装如何摄像头驱动 按照提示:点击"下一步" 教你安装如何摄像头驱动 ...

  7. Linux CMOS摄像头驱动

    1.CMOS摄像头原理 摄像头数据流向:自然景观 > 摄像头模块 > 接口 > S3C2440摄像头控制器 > LCD ov7740(摄像头模块) 输入信号:自然景观等的模拟信 ...

  8. 当腾讯会议提示“未检测到可用摄像头,请插入设备后重试”并且在设备管理器中没有发现摄像头驱动

    在搜索框中找到设备管理器这时我们并没有在设备管理器中发现摄像头的驱动 点击上面的查看按键,选择显示隐藏设备 这时我们发现设备管理器中摄像头驱动并不显示( 一定要记住摄像头的驱动信息),只需右键选择卸载 ...

  9. linux下的摄像头驱动怎么安装方法,linux下良田摄像头驱动怎么装?

    2011-07-28 回答 下载驱动精灵更新一下就行了 追问: 驱动精灵是在windows下运行的 看题目linux下 我下载了libland_10018_linux.tar.gz的驱动 怎么命令来装 ...

最新文章

  1. android 单例模式详解,android开发设计模式之——单例模式详解
  2. 一个简单遮罩弹窗效果
  3. Eclipse常用功能键
  4. Netlink 介绍(译)
  5. TensorFlow的基本使用
  6. 支付宝生成RSA密钥,上传应用公钥的完整流程
  7. lintcode最长回文子串(Manacher算法)
  8. java安全(六)java反序列化2,ysoserial调试
  9. DOCKER容器与宿主机同网段互相通信
  10. Maven : Cannot deploy artifact from the local repository
  11. td外边加div为啥不隐藏_那些不常见,但却非常实用的 css 属性
  12. php filtervar函数用法,PHP过滤器 filter_has_var() 函数用法实例分析
  13. C#获取注册表指定键值操作
  14. html下拉和收起,Vue中实现菜单下拉、收起的动画效果
  15. 基于Spark的电影推荐系统(毕业设计)
  16. Max函数、Min函数
  17. Spring Data JPA手动管理事务
  18. 学计算机的要做文档吗,电脑自学
  19. 【JavaScript笔记 · 基础篇(五)】Array全家桶(引用数据类型中的数组 / Array对象 / Array.prototype)
  20. GPRS学习(1)----网络结构及主要网元功能

热门文章

  1. 三星s5 安装android,三星galaxy s5安装不了第三方软件怎么办?如何解决
  2. 《Spark实战(第2版)》之【Spark 介绍(基于Spark 3.0)】
  3. 如何用示波器导出csv数据
  4. java actor_十分钟理解Actor模式
  5. 热闹的聚会与尴尬的聚会_如何在第一次技术聚会上生存(并蓬勃发展)
  6. 计算机硬件软件资料,计算机硬软件资料
  7. 若依学习笔记08——Xss
  8. 基于SpringMVC、Html5 的图表展示
  9. ultraiso制作u盘系统linux,UltraISO软碟通制作Fedora 17 U盘启动
  10. Web-PS入门(三)