2009年9月8日 星期二

偷拍 Framebuffer 的風流韻事

Standard
Linux 下的螢幕抓圖,多半以 Xorg/X11 底下的實作居多,有數不清的工具可以使用,但這都僅限於一般 PC 上的範疇。有許多 Embedded System 不外乎就是小而美,這種小裝置上若要跑上 Xorg,有時便不是這麼容易,因此在多數情況下 Framebuffer 才是顯示影像的主流方法。那麼我們是否可以,來實作一下 Framebuffer 的抓圖呢?嘗試著偷拍這種針孔型裝置的畫面。 :-P

這是一支簡單的程式(screenshot.c),會將 Framebuffer 的影像資料從記憶體中取出並存成 screenshot.jpg:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <jpeglib.h>

void savetojpeg(unsigned char *img, int width, int height)
{
unsigned char *buffer, *src, *dest;
unsigned char *line;
int length;
int i, line_length;
FILE *fp;
struct jpeg_compress_struct jpeg;
struct jpeg_error_mgr jerr;

/* setting error output */
jpeg.err = jpeg_std_error(&jerr);

/* create jpeg */
jpeg_create_compress(&jpeg);

/* setting image */
jpeg.image_width = width;
jpeg.image_height = height;
jpeg.input_components = 3;
jpeg.in_color_space = JCS_RGB;

/* other settings by default */
jpeg_set_defaults(&jpeg);

/* output */
fp = fopen("screenshot.jpg","w");
jpeg_stdio_dest(&jpeg, fp);

/* processing */
jpeg_start_compress(&jpeg, TRUE);

/* transform 4-byte(framebuffer raw data) to 3-byte */
length = width * height;
buffer = (unsigned char *)malloc(sizeof(unsigned char)*length*3);
for (i=0,src=img,dest=buffer;i<length;i+=3,src+=4,dest+=3)>
*dest = *src;
*(dest+1) = *(src+1);
*(dest+2) = *(src+2);
}

line_length = width * 3;
/* write image */
for (i=0,line=buffer;i<height;i++,line+=line_length)>
jpeg_write_scanlines(&jpeg, &line, 1);

jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);

/* close file */
fclose(fp);

/* release */
free(buffer);
}

int main(int argc, char* argv[], char *envp[])
{
int fb;
caddr_t mem_map;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

/* open framebuffer device */
if ((fb = open("/dev/fb0", O_RDWR))==-1) {
printf("Cannot open framebuffer device.\n");
exit(1);
}

/* get screen information */
if (ioctl(fb, FBIOGET_VSCREENINFO,&vinfo)==-1) {
printf("VSCREENINFO Error.\n");
exit(1);
}

/* get memory information of framebuffer */
if (ioctl(fb, FBIOGET_FSCREENINFO,&finfo)==-1) {
printf("FSCREENINFO Error.\n");
exit(1);
}

/* map physics address to virtual address */
mem_map = (char *)mmap(NULL, vinfo.yres*finfo.line_length,
PROT_READ, MAP_SHARED, fb, 0);
if (mem_map==MAP_FAILED) {
printf("mmap() Error. %s\n", strerror(errno));
exit(1);
}

savetojpeg(mem_map, vinfo.xres, vinfo.yres);

close(fb);

return 0;
}


然後在 comile 時只需要加上 -ljpeg 即可:
gcc -o screenshot screenshot.c -ljpeg


配合 jpeglib 的影像處理是使用 32bits,且避免過多色彩深度的轉換以混淆本文的重點,因此該程式只能在 Depth 為 32bits 的模式下正常執行。