在此已完成对linux驱动的入门,写了个小程序以实践。在此感谢朱有鹏老师,学习了老师的课程才入门驱动。
博客以记录和理解为主,并不以讲解为目的,若有需要此源码,可在博客留下邮箱信息,或联系本人。
项目名称:图片播放器
项目介绍:
小项目支持显示bmp,png,JPEG图片,并通过点击触摸屏左右两端实现上下切换图片的效果。
主要技术:
- linux Framebuffer驱动
- linux input输入子系统
- linux i2c子系统
- 触摸屏驱动
- libjpeg库
- libpng库
主要模块功能:
1.驱动模块
2.目录扫描,图片管理模块
3.图片切换模块
4.图片显示模块
模块介绍与主要代码分析
驱动模块
小项目使用s5pv210开发板,linux平台开发。
在搭建开发板平台,完成对触摸屏,显示屏支持后,驱动模块主要的任务就是进行Framebuffer地址映射。
- linux Framebuffer驱动
参考此文章理解,移植framebufer驱动。 - 触摸屏(gslX680)驱动
参考此文章理解,移植gslX680驱动,内附源码下载。 - 在此显示Framebuffer映射主要代码
int fb_driver(void)
{
int fd = -1, ret = -1;
struct fb_fix_screeninfo finfo = {0};
struct fb_var_screeninfo vinfo = {0};
// 第1步:打开设备
fd = open(FBDEVICE, O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
printf("open %s success.\n", FBDEVICE);
// 第2步:获取设备的硬件信息
ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);//打印信息
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
//打印信息
printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bpp = %u.\n", vinfo.bits_per_pixel);
// 第3步:进行mmap
unsigned long len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8;
printf("len = %ld\n", len);
pfb = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (NULL == pfb)
{
perror("mmap");
return -1;
}
printf("pfb = %p.\n", pfb);
close(fd);
return 0;
}
目录扫描,图片管理模块
目录扫描功能采用递归方法,扫描目录的所有文件,经过排除,最后只对普通文件做进一步处理。
图片管理采用数组来组织。经判后初始化数据类型。(预计是用链表实现的,可实际情况并不理想,源码中移植了linux内核链表,已验证可使用。理解可参考博客)
//声明数组节点数据结构
typedef struct picture
{
char name[PATHNAME_MAX];
image_type_e type;
}picture;
/*
*扫描目录,索引图片,并完成图片数据的初始化
*参数:目录的路径
*/
int scan_image(const char *path)
{
// 在本函数中递归检索path文件夹
//,将其中所有图片填充到iamges数组中去
DIR *dir;
struct dirent *ptr;
char base[1000];
struct stat sta;
if ((dir = opendir(path)) == NULL)
{
perror("Open dir error...");
exit(1);
}
// readdir函数每调用一次就会返回opendir打开的basepath目录下的一个文件,直到
// basepath目录下所有文件都被读完之后,就会返回NULL
while ((ptr = readdir(dir)) != NULL)
{
if(strcmp(ptr->d_name, ".")==0 || strcmp(ptr->d_name, "..")==0) ///current dir OR parrent dir
continue;
// 用lstat来读取文件属性并判断文件类型
memset(base,'\0',sizeof(base));
strcpy(base,path);
strcat(base,"/");
strcat(base,ptr->d_name);
lstat(base, &sta);
if (S_ISREG(sta.st_mode))
{
// 如果是普通文件,就要在这里进行处理:
// 处理思路就是 先判定是否属于已知的某种图片格式,如果是则放到images数组中
// 如果都不属于则不理他
if (!is_bmp(base))
{
images[image_index].type =IMAGE_TYPE_BMP;
strcpy(images[image_index].name, base);
//debug("sjk_test bmp: \n");
}
else if (!is_jpg(base))
{
images[image_index].type =IMAGE_TYPE_JPG;
strcpy(images[image_index].name, base);
//debug("sjk_test jpg: \n");
}
else if (!is_png(base))
{
images[image_index].type =IMAGE_TYPE_PNG;
strcpy(images[image_index].name, base);
//debug("sjk_test png: \n");
}
image_index++;
}
if (S_ISDIR(sta.st_mode))
{
//debug("directory.\n");
scan_image(base);
}
}
}
图片切换模块
完成触摸屏驱动后,点击左右屏幕实现切换图片。
/*
*检测触摸屏,根据结果实现图片上下切换
*参数:无传参
*/
int ts_updown(void)
{
// 第一步: 触摸屏的触摸操作检测
int fd = -1, ret = -1;
struct input_event ev;
int i = 0; // 用来记录当前显示的是第几个图片
int k =1;
fd = open(DEVICE_TOUCHSCREEN, O_RDONLY);
if (fd < 0)
{
perror("open");
return -1;
}
while (1)
{
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror("read");
close(fd);
return -1;
}
// 第二步: 根据触摸坐标来翻页
if ((ev.type == EV_ABS) && (ev.code == ABS_X))
{
// 确定这个是x坐标
if ((ev.value >= 0) && (ev.value < TOUCH_WIDTH))
{
// 上翻页
debug("up \n");
i--;
k =1;
if(i == -1){
i =image_index;
}
}
else if ((ev.value > (WIDTH - TOUCH_WIDTH)) && (ev.value <= WIDTH))
{
// 下翻页
debug("down .\n");
i++;
k=1;
if(i == (image_index)){
i =0;
}
}
}
if(k == 1)
{
debug("show \n");
show_image(i);
k =0;
}
}
close(fd);
return 0;
}
/*
*根据图片类型,调用相应的函数显示图片
*参数:在图片数组中的数组编号
*/
int show_image(int i)
{
//print_images();
//debug("--------------------i = %d\n",i);
printf("images[i].pathname = %s, type = %d.\n", images[i].name, images[i].type);
switch(images[i].type)
{
case IMAGE_TYPE_BMP:
debug("BMP \n");
display_bmp(images[i].name);
break;
case IMAGE_TYPE_JPG:
debug("JPG \n");
display_jpg(images[i].name);
break;
case IMAGE_TYPE_PNG:
debug("PNG \n");
display_png(images[i].name);
break;
case IMAGE_TPPE_UNKNOWN:
printf("unknow picture file \n");
break;
default:
break;
}
}
图片显示模块
- JPEG显示
判断是否为JPEG图片, 通过判断特点位是否为JPEG专用字符,进而确认。
/*
*测试是否为JPG图片
*参数:图片的路径
*返回值:是为0 否为1
*/
int is_jpg(const char *path)
{
FILE *file = NULL;
char buf[2] = {0};
// 打开文件
file = fopen(path, "rb");
if (NULL == file)
{
fprintf(stderr, "fopen %s error.\n", path);
fclose(file);
return -1;
}
// 读出前2个字节
fread(buf, 1, 2, file);
//debug("read: 0x%x%x\n", buf[0], buf[1]);
// 判断是不是0xffd8
if (!((buf[0] == 0xff) && (buf[1] == 0xd8)))
{
fclose(file);
// debug("%s is not a jpeg picture \n",path);
return 1; // 不是jpg图片
}
// 是0xffd8开头,就继续
// 文件指针移动到倒数2个字符的位置
fseek(file, -2, SEEK_END);
// 读出2个字节
fread(buf, 1, 2, file);
//debug("read: 0x%x%x\n", buf[0], buf[1]);
// 判断是不是0xffd9
if (!((buf[0] == 0xff) && (buf[1] == 0xd9)))
{
fclose(file);
return 1; // 不是jpg图片
}
fclose(file);
return 0;
}
确认为JPEG图片后,显示图片。代码参考jpeglib里的example.c。
(有关错误处理方面和错误跳转不太理解,但通过空函数和空指针得以解决此问题)
struct my_error_mgr
{
struct jpeg_error_mgr pub; /* "public" fields */
// jmp_buf setjmp_buffer; /* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;
void my_error_exit (j_common_ptr cinfo)
{
/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
my_error_ptr myerr = (my_error_ptr) cinfo->err;
/* Always display the message. */
/* We could postpone this until after returning, if we chose. */
(*cinfo->err->output_message) (cinfo);
/* Return control to the setjmp point */
// longjmp(myerr->setjmp_buffer, 1);
}
/*
*显示jpg图片
*参数:图片的路径
*/
int display_jpg (char * filename)
{
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
/* More stuff */
FILE * infile; /* source file */
//JSAMPARRAY buffer; /* Output row buffer */
char * buffer =NULL;
char * buffer1 =NULL;
int row_stride; /* physical row width in output buffer */
int x = 0;
int y =0;
int cnt=0;
int a =0;
if ((infile = fopen(filename, "rb")) == NULL)
{
fprintf(stderr, "can't open %s\n", filename);
return 0;
}
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
row_stride = cinfo.output_width * cinfo.output_components;
buffer = (char *)malloc(row_stride);
buffer1 = (char *)malloc(row_stride *cinfo.output_height);
if(buffer1 ==NULL && buffer ==NULL)
debug("buf after \n");
while (cinfo.output_scanline < cinfo.output_height)
{
jpeg_read_scanlines(&cinfo, &buffer, 1);
memcpy(buffer1 + (cinfo.output_scanline-1) * row_stride , buffer , row_stride);
}
for (y=0; y<cinfo.output_height ;y++)
{
for (x=0; x<cinfo.output_width; x++)
{
cnt = cinfo.output_width * y + x;
*(pfb + cnt) = ((buffer1[a+0]<<0) | (buffer1[a+1]<<8)| (buffer1[a+2]<<16));
a += 3;
}
}
jpeg_finish_decompress(&cinfo);
/* Step 8: Release JPEG decompression object */
/* This is an important step since it will release a good deal of memory. */
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 1;
}
- BMP显示
通过判断头两个字符是否为”BM”,判断是否为BMP图片
/*
*测试是否为bmp图片
*参数:图片的路径
*返回值:是为0 否为1
*/
int is_bmp(const char *path)
{
int fd = -1;
unsigned char buf[2] = {0};
ssize_t ret = 0;
// 第一步: 打开bmp图片
fd = open(path, O_RDONLY);
if (fd < 0)
{
fprintf(stderr, "open %s error.\n", path);
return -1;
}
// 第二步: 读取文件头信息
ret = read(fd, buf, 2);
if (ret != 2)
{
fprintf(stderr, "read file header error.\n");
ret = -1;
goto close;
}
// 解析头
// 第三步: 判断是不是BMP图片
if ((buf[0] != 'B') || (buf[1] != 'M'))
{
//fprintf(stderr, "file %s is not a bmp picture.\n", path);
// printf("%s is not a bmp picture \n",path);
ret = 1;
goto close;
}
else
{
ret = 0;
}
close:
close(fd);
return ret;
}
BMP本身没有对数据进行压缩,所以可直接读取数据进行操作。
char *max_sd = NULL;
extern unsigned int *pfb ;
/*
*显示bmp图片
*参数:图片的路径
*/
int display_bmp(const char *pathname)
{
pic_info bmp_info;
ClBitMapFileHeader bmp_FileHeader;
ClBitMapInfoHeader bmp_InfoHeader
unsigned int cnt = 0, a = 0;
unsigned int x, y;
int fd =-1;
int ret =-1;
char buf[2] = {0};
bmp_info.pData = max_sd;
fd = open(pathname, O_RDWR);
if (fd < 0)
{
perror("open \n");
return -1;
}
printf("open %s success.\n", FBDEVICE);
ret = read(fd ,buf ,2);
if(ret == -1)
{
perror("read bmp_FileHeader \n");
}
if(buf[0] == "B" && buf[1] == "M" )
{
debug("it is not bmp picture \n");
return -1;
}
ret = read(fd ,&bmp_FileHeader ,12);
debug("bfSize = %ld.\n", bmp_FileHeader.bfSize);
debug("bfOffBits = %ld.\n", bmp_FileHeader.bfOffBits);
ret = read(fd ,&bmp_InfoHeader ,40);
if(ret == -1)
{
perror("read bmp_InfoHeader \n");
}
bmp_info.bpp = bmp_InfoHeader.biBitCount;
bmp_info.height = bmp_InfoHeader.biHeight;
bmp_info.width = bmp_InfoHeader.biWidth ;
bmp_info.pathname = pathname;
debug("bpp = %d height = %d width = %d \n" ,bmp_info.bpp,bmp_info.height,bmp_info.width);
debug("PFB1 = %d\n",*pfb);
ret =lseek(fd, bmp_FileHeader.bfOffBits, SEEK_SET);
if(ret == -1)
{
perror("lseek \n");
}
max_sd = (char *)malloc(bmp_info.bpp* bmp_info.height* bmp_info.width / 8);
ret = read(fd , max_sd , bmp_info.bpp* bmp_info.height* bmp_info.width / 8);
for (y=0; y<bmp_info.height ;y++)
{
for (x=0; x<bmp_info.width; x++)
{
cnt = bmp_info.width *bmp_info.height -bmp_info.width * y - x;
*(pfb + cnt) = ((max_sd[a+0]<<0) | (max_sd[a+1]<<8)| (max_sd[a+2]<<16));
a += 3;
}
}
close(fd);
return 1;
}
- 显示PNG图片
通过调用libpng库函数png_sig_cmp,判断是否为PNG图片。
/*
*测试是否为png图片
*参数:图片的路径
*返回值:是为0 否为1
*/
int is_png(const char *path)
{
FILE *fp = NULL;
char buf[PNG_BYTES_TO_CHECK] = {0};
/* Open the prospective PNG file. */
if ((fp = fopen(path, "rb")) == NULL)
return -1;
/* Read in some of the signature bytes */
if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK)
return -1;
/* Compare the first PNG_BYTES_TO_CHECK bytes of the signature.
Return nonzero (true) if they match */
// png_sig_cmp return 0 if ture
if(png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK))
{
// printf("%s is no a png picture ;\n", path);
return 1;
}
return 0;
}
显示PNG图片,运用libpng库,完成PNG图片的显示。
可参考pnglib库里example.c或此博客。
/*
*显示png图片
*参数:图片的路径
*/
int display_png(char* name)
{
int i, j;
int m_width, m_height;
png_infop info_ptr; //图片信息的结构体
png_structp png_ptr; //初始化结构体,初始生成,调用api时注意传入
int a =0 ,cnt =0;
FILE* file = fopen(name, "rb"); //打开的文件名
printf("%s, %d\n", __FUNCTION__, __LINE__);
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); //创建初始化libpng库结构体
info_ptr = png_create_info_struct(png_ptr); //创建图片信息结构体
setjmp(png_jmpbuf(png_ptr)); //设置错误的返回点
// 这句很重要
png_init_io(png_ptr, file); //把文件加载到libpng库结构体中
// 读文件了
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0); //读文件内容到info_ptr中
// 得到文件的宽高色深
if ((png_ptr != NULL) && (info_ptr != NULL))
{
m_width = png_get_image_width(png_ptr, info_ptr);
m_height = png_get_image_height(png_ptr, info_ptr); //通过png库中的api获取图片的宽度和高度
printf("%s, %d, m_width =%d, m_height = %d\n", __FUNCTION__, __LINE__, m_width, m_height);
}
int color_type = png_get_color_type(png_ptr, info_ptr); //通过api获取color_type
printf("%s, %d, color_type = %d\n", __FUNCTION__, __LINE__, color_type);
int size = m_height * m_width * 4;
unsigned char *bgra = NULL;
bgra = malloc(size);
if (NULL == bgra)
{
printf("%s, %d, bgra == NULL\n", __FUNCTION__, __LINE__);
return 1;
}
int pos = 0;
// row_pointers里边就是传说中的rgb数据了
png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);
// 拷贝!!注意,如果你读取的png没有A通道,就要3位3位的读。还有就是注意字节对其的问题,最简单的就是别用不能被4整除的宽度就行了。读过你实在想用,就要在这里加上相关的对齐处理。
for(i = 0; i < m_height; i++)
{
for(j = 0; j < (4 * m_width); j += 4)
{
bgra[pos++] = row_pointers[i][j + 2]; // blue
bgra[pos++] = row_pointers[i][j + 1]; // green
bgra[pos++] = row_pointers[i][j]; // red
bgra[pos++] = row_pointers[i][j + 3]; // alpha
}
}
for (i=0; i<m_height ;i++)
{
for (j=0; j<m_width; j++)
{
cnt = WIDTH * i + j;
*(pfb + cnt) = ((bgra[a+0]<<0) | (bgra[a+1]<<8)| (bgra[a+2]<<16)| (bgra[a+3]<<24));
a += 4;
}
}
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(file);
return 0;
}
至此,linux小项目 1.1图片播放器分析完成。