一、在LCD上显示摄像头图像1_效果_框架_准备工作
1.准备工作:
(1) 准备虚拟机
(2)安装工具链
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"
(3)编译内核
① 首先解压缩内核:
tar xjf linux-3.4.2.tar.bz2
cd linux-3.4.2
② 打补丁:
可以使用我们制作好的补丁:
linux-3.4.2_camera_jz2440.patch
linux-3.4.2_camera_mini2440.patch
linux-3.4.2_camera_tq2440.patch
patch -p1 < …/linux-3.4.2_camera_jz2440.patch
③ 编译内核:
cp config_ok .config
make uImage
(重要)④ 另一种内核打补丁、编译的方法:
也可以从毕业班的内核补丁、驱动程序,自己修改、编译:
tar xjf linux-3.4.2.tar.bz2
cd linux-3.4.2
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) += s3c2410fb.o
obj-$(CONFIG_FB_S3C2410) += lcd_4.3.o
把dm9dev9000c.c、dm9000.h复制到/work/projects/linux-3.4.2/drivers/net/ethernet/davicom
修改/work/projects/linux-3.4.2/drivers/net/ethernet/davicom/Makefile
cp config_ok .config //config_ok中并没有加入UVC驱动程序,所以需要设置
make menuconfig
<*> Multimedia support --->
<*> Video For Linux
[*] Video capture adapters (NEW) --->
[*] V4L USB devices (NEW) --->
<*> USB Video Class (UVC)
// 如果你使用的是百问网自制的USB摄像头,
// 还需要参考第2课1.1.9节视频修改UVC驱动
make uImae
cp arch/arm/boot/uImage /work/nfs_root/uImage_new
(4) 文件系统:
cd /work/nfs_root
sudo tar xjf fs_mini_mdev_new.tar.bz2
sudo chown book:book fs_mini_mdev_new
(5)用新内核、新文件系统启动开发板
启动开发板至UBOOT
设置UBOOT的环境变量:
set ipaddr 192.168.1.148
set bootcmd ‘nfs 32000000 192.168.1.149:/work/nfs_root/uImage_new; bootm 32000000’
set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.149:/work/nfs_root/fs_mini_mdev_new ip=192.168.1.148
save
boot
二、在LCD上显示摄像头图像2_实现摄像头模块
数码相框部分有写好的代码,可以直接拷贝过来用。
摄像头模块即video部分代码,这部分代码实现对设备的处理,具体做法是从摄像头中将视频数据读出供后续模块处理
(1)video_manage.h、video_manage.c模块
① video_manage.h:负责抽象出所有与设备有关的结构体
#ifndef _VIDEO_MANAGER_H
#define _VIDEO_MANAGER_H
#include <config.h>
#include <pic_operation.h>
#include <linux/videodev2.h>
#define NB_BUFFER 4
/* 这里有一个问题:在VideoDevice 中引用了VideoOpr 结构体,同时在VideoOpr 结构体中的函数又引用了VideoDevice 结构体,这里交叉引用了。因此需要在最前面首先声明,后面就可以引用了 */
struct VideoDevice;
struct VideoOpr;
typedef struct VideoDevice T_VideoDevice, *PT_VideoDevice;
typedef struct VideoOpr T_VideoOpr, *PT_VideoOpr; //PT_VideoOpr为指向结构体的指针类型
struct VideoDevice { //用该结构体表示这个设备
int iFd; //记录打开设备时的文件句柄
int iPixelFormat; //摄像头视频数据的格式
int iWidth; //分辨率的宽
int iHeight; //分辨率的高
int iVideoBufCnt;
int iVideoBufMaxLen;
int iVideoBufCurIndex;
unsigned char *pucVideBuf[NB_BUFFER]; //用来存放mmap之后的地址。
/* 函数 */
PT_VideoOpr ptOPr; //ptOPr指向VideoOpr 这个结构体
};
注释:当我们在程序中构造VideoDevice 实体时,就会让ptOPr这个结构体指向我们在v4l2.c文件中构造的VideoOpr 结构体
/* v4l2.c */
/* 构造一个VideoOpr结构体 */
static T_VideoOpr g_tV4l2VideoOpr = {
.name = "v4l2",
.InitDevice = V4l2InitDevice,
.ExitDevice = V4l2ExitDevice,
.GetFormat = V4l2GetFormat,
.GetFrame = V4l2GetFrameForStreaming,
.PutFrame = V4l2PutFrameForStreaming,
.StartDevice = V4l2StartDevice,
.StopDevice = V4l2StopDevice,
};
续注释前:
typedef struct VideoBuf { //该结构体负责储存从设备中读出的frame视频数据
T_PixelDatas tPixelDatas; //从摄像头读取到的视频数据
int iPixelFormat; //从摄像头读回来的视频数据的格式(YUV或MJPEG或RGB)
}T_VideoBuf, *PT_VideoBuf;
注释:tPixelDatas结构定义在:pic_operation.h中
/* 保存图片的象素数据 */
typedef struct PixelDatas {
int iWidth; /* 宽度: 一行有多少个象素 */
int iHeight; /* 高度: 一列有多少个象素 */
int iBpp; /* 一个象素用多少位来表示 */
int iLineBytes; /* 一行数据有多少字节 */
int iTotalBytes; /* 所有字节数 */
unsigned char *aucPixelDatas; /* 象素数据真正存储的地方 */
}T_PixelDatas, *PT_PixelDatas;
续注释前:
struct VideoOpr { //与操作设备相关的各种函数
char *name;
int (*InitDevice)(char *strDevName, PT_VideoDevice ptVideoDevice);
int (*ExitDevice)(PT_VideoDevice ptVideoDevice);
/* 从ptVideoDevice设备上读取摄像头视频数据,然后存入ptVideoBuf中 */
int (*GetFrame)(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideo Buf);
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;
};
int VideoDeviceInit(char *strDevName, PT_VideoDevice ptVideoDevice);
int V4l2Init(void);
int RegisterVideoOpr(PT_VideoOpr ptVideoOpr);
int VideoInit(void);
#endif /* _VIDEO_MANAGER_H */
② video_manage.c:(参考font_manage.c)
#include <config.h>
#include <video_manager.h>
#include <string.h>
static PT_VideoOpr g_ptVideoOprHead = NULL;
/**********************************************************************
* 函数名称: RegisterVideoOpr
* 功能描述: 注册"字体模块", 所谓字体模块就是取出字符位图的方法
* 输入参数: ptVideoOpr - 一个结构体,内含"取出字符位图"的操作函数
* 输出参数: 无
* 返 回 值: 0 - 成功, 其他值 - 失败
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2013/02/08 V1.0 韦东山 创建
***********************************************************************/
int RegisterVideoOpr(PT_VideoOpr ptVideoOpr)
//该函数负责完成将在v4l2.c文件中定义的VideoOpr结构体向video_manage.c注册的过程;
//所谓注册,就是将这个结构体放入一个链表中。
{
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;
}
/**********************************************************************
* 函数名称: ShowVideoOpr
* 功能描述: 显示本程序能支持的"字体模块"
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2013/02/08 V1.0 韦东山 创建
***********************************************************************/
void ShowVideoOpr(void) //显示链表中的内容
{
int i = 0;
PT_VideoOpr ptTmp = g_ptVideoOprHead;
while (ptTmp)
{
printf("%02d %s\n", i++, ptTmp->name);
ptTmp = ptTmp->ptNext;
}
}
/**********************************************************************
* 函数名称: GetVideoOpr
* 功能描述: 根据名字取出指定的"字体模块"
* 输入参数: pcName - 名字
* 输出参数: 无
* 返 回 值: NULL - 失败,没有指定的模块,
* 非NULL - 字体模块的PT_VideoOpr结构体指针
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2013/02/08 V1.0 韦东山 创建
***********************************************************************/
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;
}
/**********************************************************************
* 函数名称: FontsInit
* 功能描述: 调用各个字体模块的初始化函数
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0 - 成功, 其他值 - 失败
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2013/02/08 V1.0 韦东山 创建
***********************************************************************/
int VideoInit(void)
{
int iError;
iError = V4l2Init();
return iError;
}
(2)v4l2.c:
①构造一个VideoOpr结构体
②注册该结构体
#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>
/* 这些格式的宏都定义在:内核文件Videodev2.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 */
/* (1)open
* (2)VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)
* (3)VIDIOC_ENUM_FMT 查询支持哪种格式
* (4)VIDIOC_S_FMT 设置摄像头使用哪种格式
* (5)VIDIOC_REQBUFS 申请buffer
对于 streaming:
* (6)VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap
* (7)VIDIOC_QBUF 放入队列
* (8)VIDIOC_STREAMON 启动设备
* (9)poll 等待有数据
* (10)VIDIOC_DQBUF 从队列中取出
* (11) 处理缓冲区中的数据
* (12)(处理过后再次放入队列中)VIDIOC_QBUF 放入队列
* ....一直进行9~12的循环
对于read,write:
read
处理....
read
* (13)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;
/* (1)open */
iFd = open(strDevName, O_RDWR);
if (iFd < 0)
{
DBG_PRINTF("can not open %s\n", strDevName);
return -1;
}
ptVideoDevice->iFd = iFd;
/* (2)VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write) */
/* 如果在写APP的时候不清楚在调用具体每个ioctl时候的参数怎么去设置,
* 就可以在内核源码中搜索相应的ioctl的宏。
* 比如搜索:VIDIOC_QUERYCAP--->Uvc_v4l2.c:struct v4l2_capability *cap = arg(ioctl传入的参数)
* 这时候就知道了如果要去查询摄像头是否为视频捕捉设备(查询摄像头属性)而去调用ioctl函数的时候传入的
* 参数应该是v4l2_capability 类型的参数
*/
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;
}
/* 是否支持streaming接口 */
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
/* 是否支持read/write接口 */
if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
DBG_PRINTF("%s supports read i/o\n", strDevName);
}
/* (3)VIDIOC_ENUM_FMT 查询支持哪种格式 */
memset(&tFmtDesc, 0, sizeof(tFmtDesc));
tFmtDesc.index = 0; //查询第1种格式
tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {
if (isSupportThisFormat(tFmtDesc.pixelformat)) //是否支持这种fmt,1支持,0不支持
{
ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat; //如果支持就将查询到的fmt赋给当前设备的fmt
break;
}
tFmtDesc.index++; //查询写一个fmt
}
if (!ptVideoDevice->iPixelFormat) //该变量一直未设置,表明摄像头的格式不能支持
{
DBG_PRINTF("can not support the format of this device\n");
goto err_exit;
}
/*(4)VIDIOC_S_FMT 设置摄像头使用哪种格式 */
/* set format in */
GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp); //事先读出LCD的分辨率、位深度等信息。
//来自disp_manage.c
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;
}
/* 当VIDIOC_S_FMT-->ioctl函数执行后,真正的分辨率应该再去读取出来 */
ptVideoDevice->iWidth = tV4l2Fmt.fmt.pix.width;
ptVideoDevice->iHeight = tV4l2Fmt.fmt.pix.height;
/* (5)VIDIOC_REQBUFS 申请buffer */
/* 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; //可以通过mmap映射到用户程序空间
iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
if (iError)
{
DBG_PRINTF("Unable to allocate buffers.\n");
goto err_exit;
}
ptVideoDevice->iVideoBufCnt = tV4l2ReqBuffs.count; //实际申请到的缓冲区个数(申请