数码相框项目学习笔记(一)

之前看了韦东山老师的数码相框项目,断断续续学完了,现在再整理回顾,做个笔记记录一下。

项目需求:

实现在开发板上显示、浏览图片文件,并能进行图片的放大、缩小、移动、连播等操作

项目的主体框架:

在这里插入图片描述

项目的主要流程:

在这里插入图片描述

硬件准备

Linux开发板、触摸屏
我的开发板是IMX6ULL,带有4.3英寸触摸屏

主函数分析

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <config.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <encoding_manager.h>
#include <fonts_manager.h>
#include <disp_manager.h>
#include <input_manager.h>
#include <pic_operation.h>
#include <render.h>
#include <picfmt_manager.h>

/* main <freetype_file> */
int main(int argc, char **argv)
{	
	int ret;

	if (argc != 2)
	{
		printf("Usage:\n");
		printf("%s <freetype_file>\n", argv[0]);
		return 0;
	}

	/* 注册显示设备 */
	DisplayInit();
	/* 可能可支持多个显示设备: 选择和初始化指定的显示设备 */
	SelectAndInitDefaultDispDev("fb");

	/* 分配5个VideoMem缓存,用于预先存放要显示的数据,以加快显示 */
	AllocVideoMem(5);

    /* 注册输入设备 */
	InputInit();
    /* 调用所有输入设备的初始化函数 */
	AllInputDevicesInit();

    /* 注册编码模块 */
    EncodingInit();

    /* 注册字库模块 */
	ret = FontsInit();
	if (ret)
	{
		printf("FontsInit error!\n");
	}

    /* 设置freetype字库所用的文件和字体尺寸 */
	ret = SetFontsDetail("freetype", argv[1], 24);
	if (ret)
	{
		printf("SetFontsDetail error!\n");
	}

    /* 注册图片文件解析模块 */
    PicFmtsInit();

    /* 注册页面 */
	PagesInit();

    /* 运行主页面 */
	Page("main")->Run(NULL);
		
	return 0;
}

当我们打开数码相框应用程序时,应该马上显示主页面,然后就根据用户的操作采取不同的动作响应。这些工作在Page(“main”)->Run(NULL);中完成。这个函数就是调用页面显示函数显示主页面,同时调用获取输入事件函数,当有输入事件产生,则调用相应的处理函数。例如,用户点击浏览模式,显示浏览界面;当点击页面中图片则显示该图片,当点击目录则进入目录并显示目录内容。
我们接下来分析下具体的模块。

模块分析

1、显示模块
首先要使图片在屏幕上显示出来,这个屏幕可以是LCD触摸屏,VGA屏等等,所以需要一个显示模块来进行显示设备的选择、初始化,获得显示设备的参数,如分辨率、像素位数、显存等,获取了这些参数后我们才可以在屏幕的某一位置描画数据。
为了方便扩展,采用面向对象的思想,先建立一个设备管理模块disp_manager,它提供了一个统一的设备结构体和一些操作函数,这个结构体中包含设备的基本信息和操作集,当有新的设备接入时,它需要提供这样一个结构体向管理模块注册。注册的具体实现是管理模块维护一个链表,链表成员是显示设备的相关结构体,当有显示设备注册时,将其提供的结构体加入链表,这样当选择显示设备时,遍历链表即可。

/* "disp_manager.h" */
typedef struct DispOpr {
	char *name;              										// 显示模块的名字
	int iXres;               										// X分辨率
	int iYres;               										// Y分辨率 
	int iBpp;                										// 像素位数 
	int iLineWidth;          										// 行字节数 
	unsigned char *DispMem;   										// 显存地址 
	int (*DeviceInit)(void);     									// 设备初始化函数 
	int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor);   // 指定像素显示颜色 
	int (*CleanScreen)(unsigned int dwBackColor);                   // 清屏为某颜色 
	int (*ShowPage)(PT_VideoMem ptVideoMem);                        // 显示一页
	struct DispOpr *ptNext;     
}T_DispOpr, *PT_DispOpr;
#include <config.h>
#include <disp_manager.h>
#include <string.h>

static PT_DispOpr g_ptDispOprHead;
static PT_DispOpr g_ptDefaultDispOpr;
static PT_VideoMem g_ptVideoMemHead;

/* 注册某个显示模块,加入到链表中(链表中存放已经注册的显示设备)*/
int RegisterDispOpr(PT_DispOpr ptDispOpr)
{
	PT_DispOpr tmp;
	/* 当前链表中尚未有显示设备 */
	if (!g_ptDispOprHead)
	{
		g_ptDispOprHead   = ptDispOpr;
		ptDispOpr->ptNext = NULL;
	}
	else
	{
	/*链表中已有一些显示设备,在链表尾加入即可 */
		tmp = g_ptDispOprHead;
		while (tmp->ptNext)
		{
			tmp = tmp->ptNext;
		}
		tmp->ptNext	  = ptDispOpr;
		ptDispOpr->ptNext = NULL;
	}

	return 0;
}

disp_manager提供了一些统一的操作函数:取出某个指定的显示设备结构体、获得设备基本信息、获得设备显存等

/* 根据名字取出指定的显示设备模块 */
PT_DispOpr GetDispOpr(char *pcName)
{
	PT_DispOpr tmp = g_ptDispOprHead;
	
	while (tmp)
	{
		if (strcmp(tmp->name, pcName) == 0)
		{
			return tmp;
		}
		tmp = tmp->ptNext;
	}
	return NULL;
}
/* 根据名字取出指定的显示设备模块,并调用初始化函数,设为默认设备*/
void SelectAndInitDefaultDispDev(char *name)
{
	g_ptDefaultDispOpr = GetDispOpr(name);
	if (g_ptDefaultDispOpr)
	{
		g_ptDefaultDispOpr->DeviceInit();
		g_ptDefaultDispOpr->CleanScreen(0);
	}
}
/* 获取显示设备的分辨率和Bpp */
int GetDispResolution(int *piXres, int *piYres, int *piBpp)
{
	if (g_ptDefaultDispOpr)
	{
		*piXres = g_ptDefaultDispOpr->iXres;
		*piYres = g_ptDefaultDispOpr->iYres;
		*piBpp  = g_ptDefaultDispOpr->iBpp;
		return 0;
	}
	else
	{
		return -1;
	}
}
/* typedef struct VideoMem {
	int iID;                        // 标识不同的页面,主页面、浏览页面...
	int bDevFrameBuffer;            // 1标识显存; 0标识普通缓存
	E_VideoMemState eVideoMemState; // VideoMem本身状态
	E_PicState ePicState;           // VideoMem中内存里图片的状态
	T_PixelDatas tPixelDatas;       // 存储数据
	struct VideoMem *ptNext;      
}T_VideoMem, *PT_VideoMem;
*/
/* 获得显存 */
PT_VideoMem GetDevVideoMem(void)
{
	PT_VideoMem tmp = g_ptVideoMemHead;
	
	while (tmp)
	{
		if (tmp->bDevFrameBuffer)
		{
			return tmp;
		}
		tmp = tmp->ptNext;
	}
	return NULL;
}

在显示过程中,如果要切换显示页面,直接获取数据在向显存中写入效率会很低,画面会有明显的卡顿,为加快显示速度,我们事先在缓存中构造好显示的页面的数据存入VideoMem,显示时再把VideoMem中的数据复制到设备的显存上 。因此可以预先准备多个VideoMem,提供一个分配VideoMem的函数。

/* VideoMem的状态:
 * 	空闲
 * 	用于预先准备显示内容
 * 	用于当前线程
 */
typedef enum {
	VMS_FREE = 0,
	VMS_USED_FOR_PREPARE,
	VMS_USED_FOR_CUR,	
}E_VideoMemState;

/* VideoMem中内存里图片的状态:
 * 空白
 * 正在生成
 * 已经生成
 */
typedef enum {
	PS_BLANK = 0,
	PS_GENERATING,
	PS_GENERATED,	
}E_PicState;

int AllocVideoMem(int iNum)
{
	int i;

	int iXres = 0;
	int iYres = 0;
	int iBpp  = 0;

	int iVMSize;
	int iLineBytes;

	PT_VideoMem ptNew;

	GetDispResolution(&iXres, &iYres, &iBpp);
	iVMSize = iXres * iYres * iBpp / 8;
	iLineBytes = iXres * iBpp / 8;

	/* 先把设备本身的framebuffer放入链表
	   将tPixelDatas.aucPixelDatas指向显示设备的framebuffer */
	ptNew = malloc(sizeof(T_VideoMem));
	if (ptNew == NULL)
	{
		return -1;
	}

	ptNew->tPixelDatas.aucPixelDatas = g_ptDefaultDispOpr->pucDispMem;
	
	ptNew->iID = 0;
	ptNew->bDevFrameBuffer = 1;       	//表明设备本身的Framebuffer
	ptNew->eVideoMemState  = VMS_FREE;  
	ptNew->ePicState	   = PS_BLANK;
	ptNew->tPixelDatas.iWidth  = iXres;
	ptNew->tPixelDatas.iHeight = iYres;
	ptNew->tPixelDatas.iBpp    = iBpp;
	ptNew->tPixelDatas.iLineBytes  = iLineBytes;
	ptNew->tPixelDatas.iTotalBytes = iVMSize;

	/* 如果下面要分配用于缓存的VideoMem, 
	 * 把设备本身framebuffer对应的VideoMem状态设置为VMS_USED_FOR_CUR,
	 * 表示这个VideoMem不会被作为缓存分配出去
	 */
	if (iNum != 0)
	{
		ptNew->eVideoMemState = VMS_USED_FOR_CUR;
	}
	
	/* 放入链表 */
	ptNew->ptNext = g_ptVideoMemHead;
	g_ptVideoMemHead = ptNew;
	

	/* 分配用于缓存的VideoMem */
	 
	for (i = 0; i < iNum; i++)
	{
		/* 分配T_VideoMem结构体本身和"跟framebuffer同样大小的缓存"
		 * 即T_VideoMem结构体后面紧跟着数据缓存区,这样ptNew+1即是数据缓冲区地址
		 */
		ptNew = malloc(sizeof(T_VideoMem) + iVMSize);
		if (ptNew == NULL)
		{
			return -1;
		}

		ptNew->tPixelDatas.aucPixelDatas = (unsigned char *)(ptNew + 1);

		ptNew->iID = 0;
		ptNew->bDevFrameBuffer = 0;
		ptNew->eVideoMemState = VMS_FREE;
		ptNew->ePicState      = PS_BLANK;
		ptNew->tPixelDatas.iWidth  = iXres;
		ptNew->tPixelDatas.iHeight = iYres;
		ptNew->tPixelDatas.iBpp    = iBpp;
		ptNew->tPixelDatas.iLineBytes = iLineBytes;
		ptNew->tPixelDatas.iTotalBytes = iVMSize;

		/* 放入链表 */
		ptNew->ptNext = g_ptVideoMemHead;
		g_ptVideoMemHead = ptNew;
	}
	
	return 0;
}

还有一些其他的函数就不一一介绍了,具体见源码。
这个项目我们用到的显示设备是触摸屏,因此需要提供一个触摸屏设备的结构体。
fb.c

#include <config.h>
#include <disp_manager.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>

static int FBDeviceInit(void);
static int FBShowPixel(int iX, int iY, unsigned int dwColor);
static int FBCleanScreen(unsigned int dwBackColor);
static int FBShowPage(PT_VideoMem ptVideoMem);


static int fd;

static struct fb_var_screeninfo FBVar;
static struct fb_fix_screeninfo FBFix;			
static unsigned char *FBMem;
static unsigned int ScreenSize;

static unsigned int LineWidth;
static unsigned int PixelWidth;

static T_DispOpr FBOpr = {
	.name        = "fb",
	.DeviceInit  = FBDeviceInit,
	.ShowPixel   = FBShowPixel,
	.CleanScreen = FBCleanScreen,
	.ShowPage    = FBShowPage,
};
/* 初始化fb */
static int FBDeviceInit(void)
{
	int ret;
	
	fd = open(FB_DEVICE_NAME, O_RDWR);
	if (0 > fd)
	{
		printf("can't open %s\n", FB_DEVICE_NAME);
	}

	//获取可变参数
	ret = ioctl(fd, FBIOGET_VSCREENINFO, &FBVar);
	if (ret < 0)
	{
		printf("can't get fb's var\n");
		return -1;
	}

	//获取固定参数
	ret = ioctl(fd, FBIOGET_FSCREENINFO, &FBFix);
	if (ret < 0)
	{
		printf("can't get fb's fix\n");
		return -1;
	}
	//计算屏幕字节大小
	ScreenSize = FBVar.xres * FBVar.yres * FBVar.bits_per_pixel / 8;
	//内存映射
	FBMem = (unsigned char *)mmap(NULL , ScreenSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (0 > FBMem)	
	{
		printf("can't mmap\n");
		return -1;
	}
	
	FBOpr.iXres       = FBVar.xres;
	FBOpr.iYres       = FBVar.yres;
	FBOpr.iBpp        = FBVar.bits_per_pixel;
	FBOpr.iLineWidth  = FBVar.xres * FBOpr.iBpp / 8;
	FBOpr.DispMem  	  = FBMem;

	LineWidth  = FBVar.xres * FBVar.bits_per_pixel / 8;
	PixelWidth = FBVar.bits_per_pixel / 8;
	
	return 0;
}


/* 指定fb上的某一像素为指定颜色 */
static int FBShowPixel(int iX, int iY, unsigned int dwColor)
{
	unsigned char *pucFB;
	unsigned short *pwFB16bpp;
	unsigned int *pdwFB32bpp;
	unsigned short wColor16bpp;
	int iRed;
	int iGreen;
	int iBlue;

	if ((iX >= FBVar.xres) || (iY >= FBVar.yres))
	{
		printf("out of region\n");
		return -1;
	}

	pucFB      = FBMem + LineWidth * iY + PixelWidth * iX;
	pwFB16bpp  = (unsigned short *)pucFB;
	pdwFB32bpp = (unsigned int *)pucFB;
	
	//根据像素位数,构建RGB
	switch (FBVar.bits_per_pixel)
	{
		case 8:
		{
			*pucFB = (unsigned char)dwColor;
			break;
		}
		case 16:
		{
			iRed   = (dwColor >> (16+3)) & 0x1f;
			iGreen = (dwColor >> (8+2)) & 0x3f;
			iBlue  = (dwColor >> 3) & 0x1f;
			wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;
			*pwFB16bpp	= wColor16bpp;
			break;
		}
		case 32:
		{
			*pdwFB32bpp = dwColor;
			break;
		}
		default :
		{
			printf("can't support %d bpp\n", FBVar.bits_per_pixel);
			return -1;
		}
	}

	return 0;
}

/* 显示一整页 */
static int FBShowPage(PT_VideoMem ptVideoMem)
{
	memcpy(FBOpr.DispMem, ptVideoMem->tPixelDatas.aucPixelDatas, ptVideoMem->tPixelDatas.iTotalBytes);
	return 0;
}

/* 清屏 */
static int FBCleanScreen(unsigned int dwBackColor)
{
	unsigned char *pucFB;
	unsigned short *pwFB16bpp;
	unsigned int *pdwFB32bpp;
	unsigned short wColor16bpp;
	int iRed;
	int iGreen;
	int iBlue;
	int i = 0;

	pucFB      = FBMem;
	pwFB16bpp  = (unsigned short *)pucFB;
	pdwFB32bpp = (unsigned int *)pucFB;

	switch (FBVar.bits_per_pixel)
	{
		case 8:
		{
			memset(FBMem, dwBackColor, ScreenSize);
			break;
		}
		case 16:
		{
			iRed   = (dwBackColor >> (16+3)) & 0x1f;
			iGreen = (dwBackColor >> (8+2)) & 0x3f;
			iBlue  = (dwBackColor >> 3) & 0x1f;
			wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;
			while (i < ScreenSize)
			{
				*pwFB16bpp	= wColor16bpp;
				pwFB16bpp++;
				i += 2;
			}
			break;
		}
		case 32:
		{
			while (i < ScreenSize)
			{
				*pdwFB32bpp	= dwBackColor;
				pdwFB32bpp++;
				i += 4;
			}
			break;
		}
		default :
		{
			printf("can't support %d bpp\n", FBVar.bits_per_pixel);
			return -1;
		}
	}

	return 0;
}

/* 注册FB设备 */
int FBInit(void)
{
	return RegisterDispOpr(&FBOpr);
}

2、输入设备模块
输入模块主要是注册输入设备,并且获取输入设备的输入事件,返回输入的数据。与上面一样,为了便于扩展,它也提供了一个管理模块,提供统一的输入设备结构体和注册、操作函数,输入设备实现此结构体并向输入管理模块注册。

typedef struct InputOpr {
	char *name;          		//输入模块名
	pthread_t tTreadID;  		//子线程ID
	int (*DeviceInit)(void);  	
	int (*DeviceExit)(void);  
	int (*GetInputEvent)(PT_InputEvent ptInputEvent); //获取输入数据
	struct InputOpr *ptNext;
}T_InputOpr, *PT_InputOpr;

输入设备当接收到输入事件时,他需要知道这个输入的具体信息,比如输入事件类型、产生时间、输入事件产生在哪个位置,对于触摸屏而言即X、Y坐标;压力值(是否按下);按键值等等。因此需要定义一个存储这些信息的结构体。

// 输入事件类型
#define INPUT_TYPE_STDIN        0
#define INPUT_TYPE_TOUCHSCREEN  1
...

typedef struct InputEvent {
	struct timeval tTime;   //时间
	int iType;  			//类型
	int iX;    				//X坐标
	int iY;					//Y坐标
	int iKey;   			//按键值
	int iPressure; 			//压力值
}T_InputEvent, *PT_InputEvent;

注册函数:

/* 注册单个输入模块 */
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
	PT_InputOpr ptTmp;

	if (!g_ptInputOprHead)
	{
		g_ptInputOprHead   = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	}
	else
	{
		ptTmp = g_ptInputOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	}

	return 0;
}

初始化输入设备并创建一个线程来获取输入事件

/* 初始化输入设备,并创建用于读取设备输入数据的子线程 */
int AllInputDevicesInit(void)
{
	PT_InputOpr ptTmp = g_ptInputOprHead;
	int ret = -1;

	while (ptTmp)
	{
		if (0 == ptTmp->DeviceInit())
		{
			/* 创建子线程 */
			pthread_create(&ptTmp->tTreadID, NULL, InputEventThreadFunction, ptTmp->GetInputEvent);			
			ret = 0;
		}
		ptTmp = ptTmp->ptNext;
	}
	return ret;
}
/* 读取设备输入数据的子线程 */
static void *InputEventThreadFunction(void *pVoid)
{
	T_InputEvent tInputEvent;
	
	/* 定义函数指针 */
	/* pVoid: ptTmp->GetInputEvent ,具体输入设备的获取输入事件函数 */
	int (*GetInputEvent)(PT_InputEvent ptInputEvent);
	GetInputEvent = (int (*)(PT_InputEvent))pVoid;

	while (1)
	{
		if(0 == GetInputEvent(&tInputEvent))
		{
			/* 唤醒主线程, 把tInputEvent的值赋给一个全局变量 */
			/* 访问临界资源前,先获得互斥量 */
			pthread_mutex_lock(&g_tMutex);
			g_tInputEvent = tInputEvent;

			/*  唤醒主线程 */
			pthread_cond_signal(&g_tConVar);

			/* 释放互斥量 */
			pthread_mutex_unlock(&g_tMutex);
		}
	}

	return NULL;
}

获取输入数据:

/* 获取输入数据,休眠状态,当输入线程读取到数据后会将其唤醒 */
int GetInputEvent(PT_InputEvent ptInputEvent)
{
	/* 休眠 */
	pthread_mutex_lock(&g_tMutex);
	pthread_cond_wait(&g_tConVar, &g_tMutex);	

	/* 被唤醒后,返回数据 */
	*ptInputEvent = g_tInputEvent;
	pthread_mutex_unlock(&g_tMutex);
	return 0;	
}

因为我们用的是触摸屏,所以注册一个触摸屏设备即可
touchscreen.c

#include <config.h>
#include <input_manager.h>
#include <disp_manager.h>
#include <stdlib.h>
//利用tslib库
#include <tslib.h>

static struct tsdev *g_tTSDev;
static int giXres;
static int giYres;

/* 初始化 */
static int TouchScreenDevInit(void)
{
	char *pcTSName = NULL;
	int iBpp;

	if ((pcTSName = getenv("TSLIB_TSDEVICE")) != NULL ) 
	{
		g_tTSDev = ts_open(pcTSName, 0);  /* 以阻塞方式打开 */
	}
	else
	{
		g_tTSDev = ts_open("/dev/event0", 1);
	}

	if (!g_tTSDev) {
		printf(APP_ERR"ts_open error!\n");
		return -1;
	}

	if (ts_config(g_tTSDev)) {
		printf("ts_config error!\n");
		return -1;
	}

	if (GetDispResolution(&giXres, &giYres, &iBpp))
	{
		return -1;
	}

	return 0;
}

/* 读取数据 */
static int TouchScreenGetInputEvent(PT_InputEvent ptInputEvent)
{
	struct ts_sample tSamp;
	int iRet;	

	while (1)
	{
	   /* #include <tslib.h>
	      int ts_read(struct tsdev *dev, struct ts_sample *samp, int nr);
       */
		iRet = ts_read(g_tTSDev, &tSamp, 1); /* 如果无数据则休眠 */
		if (iRet == 1)
		{
			ptInputEvent->tTime     = tSamp.tv;
			ptInputEvent->iType     = INPUT_TYPE_TOUCHSCREEN;
			ptInputEvent->iX        = tSamp.x;
			ptInputEvent->iY        = tSamp.y;
			ptInputEvent->iPressure = tSamp.pressure;
			return 0;
		}
		else
		{
			return -1;
		}
	}

	return 0;
}

/* 退出触摸屏模块 */
static int TouchScreenDevExit(void)
{
	return 0;
}

static T_InputOpr g_tTouchScreenOpr = {
	.name          = "touchscreen",
	.DeviceInit    = TouchScreenDevInit,
	.DeviceExit    = TouchScreenDevExit,
	.GetInputEvent = TouchScreenGetInputEvent,
};

/* 注册 */
int TouchScreenInit(void)
{
	return RegisterInputOpr(&g_tTouchScreenOpr);
}

猜你喜欢

转载自blog.csdn.net/ChenNightZ/article/details/108236025
今日推荐