目的
有时候需要记录BUG现象、保存图片等需求,所以在 JZ2440 上实现截图的功能。
顺便记录一下 png 格式的保存方法,以便以后查阅。
准备
需要使用到 linpng 的库,编译安装见以下链接
交叉编译 libpng-1.6.37 zlib-1.2.11 简单使用
实现
截图功能比较简单,只需要获得一个事件,然后将显存的 RBG 格式,保存为 png 格式就可以了,
这里为了使用方便,决定使用板上按键 S5(EINT19)作为触发截图的按键,
所以分为两个部分,驱动层申请中断获取事件通知应用层,应用层获取事件保存截图。
1. 驱动层
不知道写啥,直接贴源码吧。
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/poll.h>
#include <asm/atomic.h>
#include <mach/regs-gpio.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <asm/io.h>
typedef volatile unsigned int vuint32x; // 寄存器等定义成这个
#define BUTTON_NUM (1) // 为扩展做准备
#define BUTTON_MODULE_NAME "screenshot_button" // 设备名称
#define REG_GPGDAT 0x56000064 // EINT19 所在的 GPIO 数据寄存器
static atomic_t canopen = ATOMIC_INIT(1); // 用来确保只有一个应用程序打开该驱动
static int button_major; // 驱动主设备号
static dev_t button_dev_t; // 驱动设备号
static struct class* button_class; // 创建类
static struct device* button_class_dev; // 创建类中的设备,自动生成设备节点
static struct fasync_struct *button_fasync; // 异步通知
static vuint32x * pGpgdat; // ioremap 后的寄存器地址
struct btn_desc_def {
int irq_num; // 输入中断号
char* name; // 名字
struct timer_list timer; // 为了消抖使用
};
static struct btn_desc_def btn_s[BUTTON_NUM] = { // 支持的按键数组,为了方便只做一个
{IRQ_EINT19 , BUTTON_MODULE_NAME, { }},
};
static irqreturn_t buttons_irq(int irqnum, void * data) // EINT19 中断函数
{
struct btn_desc_def* btn = (struct btn_desc_def *)data; // 获取按键对象
mod_timer(&btn->timer , jiffies + (HZ * 75 / 1000)); // 定时 75 ms
return IRQ_HANDLED;
}
static void btn_timer_fun(unsigned long __input_data)
{
//struct btn_desc_def * const btndesc = (struct btn_desc_def * const)__input_data;
//struct timer_list * timer = &btndesc->timer;
//static int drv_cnt = 0;printk("drv_cnt = %d\n",drv_cnt++);
if (*pGpgdat & (1 << 11)) // 使用过程发现不消抖会触发多次,这里偷懒没有将该数据放入按键数组中
return; // 按键定时后,状态不是按下,直接返回
kill_fasync(&button_fasync, SIGIO, POLL_IN); // 异步通知
}
static int button_module_open(struct inode *pnode, struct file *pfile)
{
int ret, i;
if(!atomic_dec_and_test(&canopen)) { // 确保只有一个应用程序打开该设备
atomic_inc(&canopen);
return -EBUSY;
}
printk("button_module_open button_major : %d\n", button_major);
pGpgdat = ioremap(REG_GPGDAT, 4);
for(i=0;i<BUTTON_NUM;i++) {
ret = request_irq(btn_s[i].irq_num , buttons_irq, IRQF_TRIGGER_FALLING,
btn_s[i].name, &btn_s[i]); // 申请中断
init_timer(&btn_s[i].timer); // 初始化定时器
btn_s[i].timer.function = btn_timer_fun; // 定时器执行函数
btn_s[i].timer.data = (unsigned long)&btn_s[i]; // 定时器执行函数传入的参数
}
return 0;
}
static int button_module_release(struct inode * pnode, struct file *pfile)
{
int i;
printk("button_module_release button_major : %d\n", button_major);
for(i=0;i<BUTTON_NUM;i++) {
free_irq(btn_s[i].irq_num, &btn_s[i]);
del_timer(&btn_s[i].timer);
}
iounmap(pGpgdat);
atomic_inc(&canopen); // 可以让其他应用程序打开
return 0;
}
static int button_module_fasync(int fd, struct file * file, int on)
{
// 成功返回 0 ,失败返回 小于0
return fasync_helper(fd, file, on, &button_fasync);
}
static struct file_operations button_module_fops = {
.owner = THIS_MODULE,
.open = button_module_open,
.release= button_module_release,
.fasync = button_module_fasync,
};
static int __init button_module_init(void)
{
button_major = register_chrdev(0, BUTTON_MODULE_NAME, &button_module_fops);
button_dev_t = MKDEV(button_major, 0);
printk(BUTTON_MODULE_NAME " button_module_init button_major : %d\n", button_major);
button_class = class_create(THIS_MODULE, BUTTON_MODULE_NAME);
if (IS_ERR(button_class)) {
printk("button_class error %ld\n", IS_ERR(button_class));
return -1;
}
button_class_dev = device_create(button_class, NULL,
button_dev_t, NULL, BUTTON_MODULE_NAME);
if (IS_ERR(button_class_dev)) {
printk("button_class_dev error %ld\n",IS_ERR(button_class_dev));
return -1;
}
return 0;
}
static void __exit button_module_exit(void)
{
printk(BUTTON_MODULE_NAME" button_module_exit\n");
unregister_chrdev(button_major,BUTTON_MODULE_NAME);
device_destroy(button_class, button_dev_t);
class_destroy(button_class);
}
module_init(button_module_init);
module_exit(button_module_exit);
MODULE_LICENSE("GPL");
2. 应用层
a. 获取显存
获取显示屏的信息,并且使用 mmap 函数来获取显存地址。
#include <linux/fb.h>
#include <sys/mman.h>
#define FB_DEVICE_NAME "/dev/fb0" // 显示设备节点名称
static int g_fb_fd; // 显示设备文件描述符
static struct fb_var_screeninfo g_tFBVar; // 显示设备可变参数
static struct fb_fix_screeninfo g_tFBFix; // 显示设备固定参数
static unsigned char *g_pucFBMem; // 显存地址
static unsigned int g_dwScreenSize; // 显存大小 = xres * yres * bpp / 8
static unsigned int g_dwLineWidth; // 一行长度 = xres * bpp / 8
static unsigned int g_dwPixelWidth; // 像素长度 = bpp / 8
static int FBDeviceInit()
{
int ret;
g_fb_fd = open(FB_DEVICE_NAME, O_RDWR); // 可读可写
ret = ioctl(g_fb_fd, FBIOGET_VSCREENINFO, &g_tFBVar);
ret = ioctl(g_fb_fd, FBIOGET_FSCREENINFO, &g_tFBFix);
g_dwScreenSize = g_tFBVar.xres * g_tFBVar.yres * g_tFBVar.bits_per_pixel / 8;
g_pucFBMem = (unsigned char *)mmap(NULL , g_dwScreenSize, PROT_READ | PROT_WRITE, MAP_SHARED, g_fb_fd, 0);
g_dwLineWidth = g_tFBVar.xres * g_tFBVar.bits_per_pixel / 8;
g_dwPixelWidth = g_tFBVar.bits_per_pixel / 8;
printf("g_pucFBMem = 0x%08X\n",(int)g_pucFBMem);
printf("g_dwScreenSize = 0x%08X\n", g_dwScreenSize);
printf("xres = %d yres = %d\n", g_tFBVar.xres, g_tFBVar.yres);
printf("g_dwLineWidth = %d\ng_dwPixelWidth = %d\n", g_dwLineWidth, g_dwPixelWidth);
return 0;
}
b. 使用 libpng 保存图片
根据 libpng 库中的 example.c 修改
#include <zlib.h>
#include <png.h>
static int screenshot(const char* file_name) // 保存文件名
{
FILE *fp;
png_structp png_ptr; // png 对象
png_infop info_ptr; // png 文件头信息
int row;
// 打开文件
fp = fopen(file_name, "wb");
if (fp == NULL) {
printf("fopen error !\n");
return -1;
}
// 创建 png 对象,使用 longjmp 方法获取异常时后面三个参数填 NULL
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL,NULL);
if (png_ptr == NULL) {
printf("png_create_write_struct error !\n");
fclose(fp);
return -1;
}
// 创建 png 文件头部信息
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
printf("png_create_info_struct error !\n");
fclose(fp);
png_destroy_write_struct(&png_ptr, NULL);
return -1;
}
// 设置异常捕获点
if (setjmp(png_jmpbuf(png_ptr))) {
/* If we get here, we had a problem writing the file. */
printf("long jump error !\n");
fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
return -1;
}
// 告知 png 对象要写入的文件
png_init_io(png_ptr, fp);
// 设置 png 文件头部信息,这里保存的时 RGBA 文件
png_set_IHDR(png_ptr, info_ptr, g_tFBVar.xres, g_tFBVar.yres, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
// 将 png 头部信息写入文件
png_write_info(png_ptr, info_ptr);
// 我也不知道什么意思
png_set_packing(png_ptr);
// 只是用来显示一下刚才设置的信息
//png_uint_32 width, height;
//int bit_depth, color_type, interlace_type;
//png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL);
//printf("\twidth=%d,height=%d,bit_depth=%d\n\tcolor_type=%d,interlace_type=%d\n", width, height, bit_depth, color_type, interlace_type);
// png RGBA 格式一行数据,一像素需要四个字节
png_bytep const rowbuf = malloc(g_tFBVar.xres << 2);
png_bytep fbmem; // 显存每行开始的地址
png_uint_16 color; // 这里偷懒了,没有对各种 bpp 进行判断,直接使用了 16bpp
int i, j;
for(row = 0;row < g_tFBVar.yres;row++) {
// 获取行首地址
fbmem = g_pucFBMem + row * g_tFBVar.xres * (g_tFBVar.bits_per_pixel >> 3);
i = j = 0;
while(i < g_tFBVar.xres * 2) { // 将显存数据转换成 RGBA 格式
color = (fbmem[i+1] << 8) | fbmem[i]; // 16bpp 颜色
i += 2;
rowbuf[j++] = (png_byte)((color >> 8) & 0xF8);
rowbuf[j++] = (png_byte)((color >> 3) & 0xFC);
rowbuf[j++] = (png_byte)((color << 3) & 0xF8);
rowbuf[j++] = 0xFF; // 透明度:0xFF 不透明
}
// 该行写入文件
png_write_row(png_ptr, rowbuf);
}
free(rowbuf);
// 写入 png 尾部信息
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr); // 销毁对象
fclose(fp); // 释放文件
return 0;
}
3. 编译
编译时加上 -lz -lpng 指明所使用的库。
arm-linux-gcc -o screenshot screenshot.c -lz -lpng
测试
支持鼠标测试时的截图:
预读文件同时拖拽的截图: