arm-linux 裸机下 VNC 的实现
这里的 arm-linux 裸机指的是,只有基本 C 库和安装了 busybox 的嵌入式系统,没有 X11 或者 wayland 作为底层支援。
这里的实现是基于 framebuffer 的,是将用于 LCD 显示的 /dev/fb* 设备中数据进行了拷贝(其实是 mmap 进行了数据共享,是直接取了 framebuffer 数据缓存),并通过 VNC 协议外发。本文只记录了一个没有键盘鼠标的,基于 framebuffer 的 VNC 桌面实验。本实验中,使用友善之臂设计的 s5pv21 开发板。
今天目的只是实验是否可行,并没有详细 debug。这个明显是还有很大改进空间的。PS: 原来 VNC 的协议本身就是叫 rfb, remote framebuffer。
下面是在 xtightvncviewer 中连接效果:
实现路径:
1. 为 arm 板编译安装了 libvncserver。编译的时候,进行了下面一些特殊操作:
a. 有部分可以在 configure 时候关闭(比如 --without-gnutls)
b. 对 SDL 和 GTK 的需求,我直接编辑了 configure 脚本,找到 HAVE_LIBSDL_FALSE= ,HAVE_LIBGTK_FALSE= 位置,去掉了外面的判断。即默认就是这两个状态。因为我不需要 GTK 和 SDL。
c. 解压出来的工具无法正确判断系统类型,configure 时候指定了系统: build_os=gnu-linux ./configure --host=arm-linux --prefix=/tmp/install
编译下面的文件,将二进制拷贝到开发板。
该文件是从github获得的,作者实现的时候把键盘鼠标都删掉了,不过这个明显是可以添加的。网址:
/* * $Id$ * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * This project is an adaptation of the original fbvncserver for the iPAQ * and Zaurus. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/sysmacros.h> /* For makedev() */ #include <fcntl.h> #include <linux/fb.h> #include <linux/input.h> #include <assert.h> #include <errno.h> /* libvncserver */ #include "rfb/rfb.h" //#include "rfb/keysym.h" /*****************************************************************************/ //#define LOG_FPS #define BITS_PER_SAMPLE 5 #define SAMPLES_PER_PIXEL 2 static char fb_device[256] = "/dev/fb0"; static struct fb_var_screeninfo scrinfo; static int fbfd = -1; static unsigned short int *fbmmap = MAP_FAILED; static unsigned short int *vncbuf; static unsigned short int *fbbuf; static int vnc_port = 5900; static rfbScreenInfoPtr server; static size_t bytespp; /* No idea, just copied from fbvncserver as part of the frame differerencing * algorithm. I will probably be later rewriting all of this. */ static struct varblock_t { int min_i; int min_j; int max_i; int max_j; int r_offset; int g_offset; int b_offset; int rfb_xres; int rfb_maxy; } varblock; /*****************************************************************************/ static void init_fb(void) { size_t pixels; if ((fbfd = open(fb_device, O_RDONLY)) == -1) { fprintf(stderr, "cannot open fb device %s\n", fb_device); exit(EXIT_FAILURE); } if (ioctl(fbfd, FBIOGET_VSCREENINFO, &scrinfo) != 0) { fprintf(stderr, "ioctl error\n"); exit(EXIT_FAILURE); } pixels = scrinfo.xres * scrinfo.yres; bytespp = scrinfo.bits_per_pixel / 8; fprintf(stderr, "xres=%d, yres=%d, xresv=%d, yresv=%d, xoffs=%d, yoffs=%d, bpp=%d\n", (int)scrinfo.xres, (int)scrinfo.yres, (int)scrinfo.xres_virtual, (int)scrinfo.yres_virtual, (int)scrinfo.xoffset, (int)scrinfo.yoffset, (int)scrinfo.bits_per_pixel); fprintf(stderr, "offset:length red=%d:%d green=%d:%d blue=%d:%d \n", (int)scrinfo.red.offset, (int)scrinfo.red.length, (int)scrinfo.green.offset, (int)scrinfo.green.length, (int)scrinfo.blue.offset, (int)scrinfo.blue.length ); fbmmap = mmap(NULL, pixels * bytespp, PROT_READ, MAP_SHARED, fbfd, 0); if (fbmmap == MAP_FAILED) { fprintf(stderr, "mmap failed\n"); exit(EXIT_FAILURE); } } static void cleanup_fb(void) { if(fbfd != -1) { close(fbfd); } } /*****************************************************************************/ static void init_fb_server(int argc, char **argv) { fprintf(stderr, "Initializing server...\n"); /* Allocate the VNC server buffer to be managed (not manipulated) by * libvncserver. */ vncbuf = calloc(scrinfo.xres * scrinfo.yres, bytespp); assert(vncbuf != NULL); /* Allocate the comparison buffer for detecting drawing updates from frame * to frame. */ fbbuf = calloc(scrinfo.xres * scrinfo.yres, bytespp); assert(fbbuf != NULL); /* TODO: This assumes scrinfo.bits_per_pixel is 16. */ server = rfbGetScreen(&argc, argv, scrinfo.xres, scrinfo.yres, BITS_PER_SAMPLE, SAMPLES_PER_PIXEL, bytespp); assert(server != NULL); server->desktopName = "framebuffer"; server->frameBuffer = (char *)vncbuf; server->alwaysShared = TRUE; server->httpDir = NULL; server->port = vnc_port; // server->kbdAddEvent = keyevent; // server->ptrAddEvent = ptrevent; rfbInitServer(server); /* Mark as dirty since we haven't sent any updates at all yet. */ rfbMarkRectAsModified(server, 0, 0, scrinfo.xres, scrinfo.yres); /* No idea. */ varblock.r_offset = scrinfo.red.offset + scrinfo.red.length - BITS_PER_SAMPLE; varblock.g_offset = scrinfo.green.offset + scrinfo.green.length - BITS_PER_SAMPLE; varblock.b_offset = scrinfo.blue.offset + scrinfo.blue.length - BITS_PER_SAMPLE; varblock.rfb_xres = scrinfo.yres; varblock.rfb_maxy = scrinfo.xres - 1; } // sec #define LOG_TIME 5 int timeToLogFPS() { static struct timeval now={0,0}, then={0,0}; double elapsed, dnow, dthen; gettimeofday(&now,NULL); dnow = now.tv_sec + (now.tv_usec /1000000.0); dthen = then.tv_sec + (then.tv_usec/1000000.0); elapsed = dnow - dthen; if (elapsed > LOG_TIME) memcpy((char *)&then, (char *)&now, sizeof(struct timeval)); return elapsed > LOG_TIME; } /*****************************************************************************/ //#define COLOR_MASK 0x1f001f #define COLOR_MASK (((1 << BITS_PER_SAMPLE) << 1) - 1) #define PIXEL_FB_TO_RFB(p,r_offset,g_offset,b_offset) ((p>>r_offset)&COLOR_MASK) | (((p>>g_offset)&COLOR_MASK)<<BITS_PER_SAMPLE) | (((p>>b_offset)&COLOR_MASK)<<(2*BITS_PER_SAMPLE)) static void update_screen(void) { #ifdef LOG_FPS static int frames = 0; frames++; if(timeToLogFPS()) { double fps = frames / LOG_TIME; fprintf(stderr, " fps: %f\n", fps); frames = 0; } #endif varblock.min_i = varblock.min_j = 9999; varblock.max_i = varblock.max_j = -1; uint32_t *f = (uint32_t *)fbmmap; /* -> framebuffer */ uint32_t *c = (uint32_t *)fbbuf; /* -> compare framebuffer */ uint32_t *r = (uint32_t *)vncbuf; /* -> remote framebuffer */ int xstep = 4/bytespp; int y; for (y = 0; y < (int)scrinfo.yres; y++) { /* Compare every 1/2/4 pixels at a time */ int x; for (x = 0; x < (int)scrinfo.xres; x += xstep) { uint32_t pixel = *f; if (pixel != *c) { *c = pixel; #if 0 /* XXX: Undo the checkered pattern to test the efficiency * gain using hextile encoding. */ if (pixel == 0x18e320e4 || pixel == 0x20e418e3) pixel = 0x18e318e3; #endif *r = PIXEL_FB_TO_RFB(pixel, varblock.r_offset, varblock.g_offset, varblock.b_offset); if (x < varblock.min_i) varblock.min_i = x; else { if (x > varblock.max_i) varblock.max_i = x; if (y > varblock.max_j) varblock.max_j = y; else if (y < varblock.min_j) varblock.min_j = y; } } f++; c++; r++; } } if (varblock.min_i < 9999) { if (varblock.max_i < 0) varblock.max_i = varblock.min_i; if (varblock.max_j < 0) varblock.max_j = varblock.min_j; fprintf(stderr, "Dirty page: %dx%d+%d+%d...\n", (varblock.max_i+2) - varblock.min_i, (varblock.max_j+1) - varblock.min_j, varblock.min_i, varblock.min_j); rfbMarkRectAsModified(server, varblock.min_i, varblock.min_j, varblock.max_i + 2, varblock.max_j + 1); rfbProcessEvents(server, 10000); } } /*****************************************************************************/ void print_usage(char **argv) { fprintf(stderr, "%s [-f device] [-p port] [-h]\n" "-p port: VNC port, default is 5900\n" "-f device: framebuffer device node, default is /dev/fb0\n" "-h : print this help\n" , *argv); } int main(int argc, char **argv) { if(argc > 1) { int i=1; while(i < argc) { if(*argv[i] == '-') { switch(*(argv[i] + 1)) { case 'h': print_usage(argv); exit(0); break; case 'f': i++; strcpy(fb_device, argv[i]); break; case 'p': i++; vnc_port = atoi(argv[i]); break; } } i++; } } fprintf(stderr, "Initializing framebuffer device %s...\n", fb_device); init_fb(); fprintf(stderr, "Initializing VNC server:\n"); fprintf(stderr, " width: %d\n", (int)scrinfo.xres); fprintf(stderr, " height: %d\n", (int)scrinfo.yres); fprintf(stderr, " bpp: %d\n", (int)scrinfo.bits_per_pixel); fprintf(stderr, " port: %d\n", (int)vnc_port); init_fb_server(argc, argv); /* Implement our own event loop to detect changes in the framebuffer. */ while (1) { while (server->clientHead == NULL) rfbProcessEvents(server, 100000); rfbProcessEvents(server, 100000); update_screen(); } fprintf(stderr, "Cleaning up...\n"); cleanup_fb(); }