韦东山第3期嵌入式Linux项目-视频监控-3-在LCD上显示摄像头图像

本文档详细介绍了如何在嵌入式Linux系统中通过LCD显示摄像头图像,包括准备虚拟机,安装工具链,编译内核,实现摄像头模块,视频转换,整合代码,调试测试以及在PC上显示图像的步骤。主要涉及Linux内核打补丁、编译,以及视频数据的读取、转换和显示。
摘要由CSDN通过智能技术生成

一、在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;  //实际申请到的缓冲区个数(申请
  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值