1. 程式人生 > >手機螢幕實時在PC端顯示

手機螢幕實時在PC端顯示

主要包括Android手機端和PC端。

1.手機APP(Client)


2.桌面端軟體(server):

(1)桌面軟體介面如下:


(2)當有客戶端(安卓手機)連線時,會自動彈出一個跟手機螢幕大小相等的視窗,實時顯示得到螢幕截圖。

3.實時顯示展示

 

上圖是在activity介面下拉通知欄,然後右側DDMS顯示heap裡面的記憶體分配變化,基本變化不大,還是比較穩定的。

 

上圖是兩個手機連線PC時手機端的螢幕共享,左圖是Sony MT15i,安卓4.04的系統

右圖是Galaxy S I9000,安卓4.2.2的系統。目前只有兩部手機,測試的話沒有大的問題,多部手機的話PC端的處理方式類似,基本無需改動程式碼。

root手機的螢幕共享問題。按下屬步驟操作即可:


命令依次為(#為terminal的命令提示符):

# adb shell

# su

# chmod 777 /data/local

# exit

4.相關程式碼解析:

(1)讀取linux裝置顯示的framebuffer, 以及對相應的資料進行顏色轉換:

#include "fbbuffer.h"

static int waitbfg=0; /* wait before grabbing (for -C )... */


/* some conversion macros */
#define RED565(x)    ((((x) >> (11 )) & 0x1f) << 3)
#define GREEN565(x)  ((((x) >> (5 )) & 0x3f) << 2)
#define BLUE565(x)   ((((x) >> (0)) & 0x1f) << 3)

#define ALPHA1555(x) ((((x) >> (15)) & 0x1 ) << 0)
#define RED1555(x)   ((((x) >> (10)) & 0x1f) << 3)
#define GREEN1555(x) ((((x) >> (5 )) & 0x1f) << 3)
#define BLUE1555(x)  ((((x) >> (0 )) & 0x1f) << 3)

void FatalError(char* err){
	fprintf(stderr,"An error occured: %s %s\nExiting now...\n",err,strerror(errno));
	fflush(stderr);
	exit (1);
}

void Usage(char *binary){
	printf("Usage: %s [-ghi] [-{C|c} vt] [-d dev] [-s n] filename.png\n", binary);
}

void Help(char *binary){
	printf("\t\tby Dariusz Swiderski <
[email protected]
>\n\n"); Usage(binary); printf("\nPossible options:\n"); printf("\t-C n \tgrab from console n, for slower framebuffers\n"); printf("\t-c n \tgrab from console n\n"); printf("\t-d dev\tuse framebuffer device dev instead of default\n"); /* not supported as for now printf("\t-g \tsave a grayscaled PNG\n"); */ printf("\t-h \tprint this usage information\n"); printf("\t-i \tturns OFF interlacing\n"); printf("\t-s n \tsleep n seconds before making screenshot\n"); printf("\nSend feedback !!!\n"); } void chvt(int num){ int fd; if(!(fd = open("/dev/console", O_RDWR))) FatalError("cannot open /dev/console"); if (ioctl(fd, VT_ACTIVATE, num)) FatalError("ioctl VT_ACTIVATE "); if (ioctl(fd, VT_WAITACTIVE, num)) FatalError("ioctl VT_WAITACTIVE"); close(fd); if (waitbfg) sleep (3); } unsigned int create_bitmask(struct fb_bitfield* bf) { return ~(~0u << bf->length) << bf->offset; } // Unifies the picture's pixel format to be 32-bit ARGB void unify(struct picture *pict, struct fb_var_screeninfo *fb_varinfo) { __u32 red_mask, green_mask, blue_mask; __u32 c; __u32 r, g, b; __u32* out; int i, j = 0, bytes_pp; // build masks for extracting colour bits red_mask = create_bitmask(&fb_varinfo->red); green_mask = create_bitmask(&fb_varinfo->green); blue_mask = create_bitmask(&fb_varinfo->blue); // go through the image and put the bits in place out = (__u32*)malloc(pict->xres * pict->yres * sizeof(__u32)); bytes_pp = pict->bps >> 3; for (i = 0; i < pict->xres * pict->yres * bytes_pp; i += bytes_pp) { memcpy (((char*)&c) + (sizeof(__u32) - bytes_pp), pict->buffer + i, bytes_pp); // get the colors r = ((c & red_mask) >> fb_varinfo->red.offset) & ~(~0u << fb_varinfo->red.length); g = ((c & green_mask) >> fb_varinfo->green.offset) & ~(~0u << fb_varinfo->green.length); b = ((c & blue_mask) >> fb_varinfo->blue.offset) & ~(~0u << fb_varinfo->blue.length); // format the new pixel out[j++] = (0xFF << 24) | (b << 16) | (g << 8) | r; } free(pict->buffer); pict->buffer = (char*)out; pict->bps = 32; } int read_fb(char *device, struct picture *pict) { int fd, i,j; struct fb_fix_screeninfo fb_fixinfo; struct fb_var_screeninfo fb_varinfo; if(!(fd=open(device, O_RDONLY))) D("Couldn't open framebuffer device"); if (ioctl(fd, FBIOGET_FSCREENINFO, &fb_fixinfo)) D("ioctl FBIOGET_FSCREENINFO"); if (ioctl(fd, FBIOGET_VSCREENINFO, &fb_varinfo)) D("ioctl FBIOGET_VSCREENINFO"); pict->xres=fb_varinfo.xres; pict->yres=fb_varinfo.yres; pict->bps=fb_varinfo.bits_per_pixel; pict->gray=fb_varinfo.grayscale; if(fb_fixinfo.visual==FB_VISUAL_PSEUDOCOLOR){ pict->colormap=(struct fb_cmap*)malloc(sizeof(struct fb_cmap)); pict->colormap->red=(__u16*)malloc(sizeof(__u16)*(1<<pict->bps)); pict->colormap->green=(__u16*)malloc(sizeof(__u16)*(1<<pict->bps)); pict->colormap->blue=(__u16*)malloc(sizeof(__u16)*(1<<pict->bps)); pict->colormap->transp=(__u16*)malloc(sizeof(__u16)*(1<<pict->bps)); pict->colormap->start=0; pict->colormap->len=1<<pict->bps; if (ioctl(fd, FBIOGETCMAP, pict->colormap)) D("ioctl FBIOGETCMAP"); } switch(pict->bps){ case 15: i=2; break; default: i=pict->bps>>3; } if(!(pict->buffer=malloc(pict->xres*pict->yres*i))) D("couldnt malloc."); fflush(stdout); if(read(fd, pict->buffer, ((pict->xres * pict->yres) * i) )!=(pict->xres * pict->yres *i )) D("couldn't read fb0."); close (fd); unify(pict, &fb_varinfo); return 0; } void convert8to32(struct picture *pict){ int i; int j=0; __u8 c; char *out=(char*)malloc(pict->xres*pict->yres*4); for (i=0; i<pict->xres*pict->yres; i++) { c = ((__u8*)(pict->buffer))[i]; out[j++]=(char)(pict->colormap->red[c]); out[j++]=(char)(pict->colormap->green[c]); out[j++]=(char)(pict->colormap->blue[c]); out[j++]=(char)(pict->colormap->transp[c]); } free(pict->buffer); pict->buffer=out; } void convert1555to32(struct picture *pict){ int i; int j=0; __u16 t,c; char *out=(char*)malloc(pict->xres*pict->yres*4); for (i=0; i<pict->xres*pict->yres; i++) { c = ( (__u16*)(pict->buffer))[i]; out[j++]=(char)RED1555(c); out[j++]=(char)GREEN1555(c); out[j++]=(char)BLUE1555(c); out[j++]=(char)ALPHA1555(c); } free(pict->buffer); pict->buffer=out; } void convert565to32(struct picture *pict){ int i; int j=0; __u16 t,c; char *out=(char*)malloc(pict->xres*pict->yres*4); for (i=0; i<pict->xres*pict->yres; i++) { c = ( (__u16*)(pict->buffer))[i]; out[j++]=(char)0xff; out[j++]=(char)RED565(c); out[j++]=(char)GREEN565(c); out[j++]=(char)BLUE565(c); } free(pict->buffer); pict->buffer=out; } int TakeScreenshot (char* device, struct picture* pict) { return read_fb(device, pict); } unsigned char* read_fb_buffer() { D("read fb0"); char* device; device ="/dev/graphics/fb0"; struct picture pict, temp_pict; if (TakeScreenshot(device, &pict) < 0) D("Take screen failed."); D("read fb0 successfully"); int length = pict.xres * pict.yres * pict.bps / 8; int seek, len; int temp_seak,temp_len, index = 0; int temp_length = length/4; temp_pict.xres = pict.xres/2; temp_pict.yres = pict.yres/2; int linebytes = pict.xres*pict.bps/8; temp_pict.buffer = malloc(temp_length*sizeof(char)); short nextline = 0; for(temp_seak=0;temp_seak<(temp_length-4);temp_seak += 4){ temp_pict.buffer[temp_seak] = (pict.buffer[index]+pict.buffer[index+4])/2; temp_pict.buffer[temp_seak+1] = (pict.buffer[index+1]+pict.buffer[index+5])/2; temp_pict.buffer[temp_seak+2] = (pict.buffer[index+2]+pict.buffer[index+6])/2; temp_pict.buffer[temp_seak+3] = (pict.buffer[index+3]+pict.buffer[index+7])/2; if(((index+8)%(linebytes) == 0)&&nextline==1) { index += linebytes+8; nextline = 0; } else { index += 8; nextline = 1; } } free(pict.buffer); D("Size %d",temp_length); return temp_pict.buffer; // return pict.buffer; } void freefbb(unsigned char* fb) { free(fb); }

(2)android應用層通過jni介面接收native層返回的資料,程式碼如下:
JNIEXPORT jint JNICALL Java_com_fb_screenshotapp_fbjni_bufferlength
  (JNIEnv *env, jclass jca, jstring path ){
	jint ret = 10;
    return ret;
}

JNIEXPORT jbyteArray JNICALL Java_com_fb_screenshotapp_fbjni_screenPngBytes
  (JNIEnv *env, jclass jca,jint size){

    unsigned char* fbb = read_fb_buffer();
    jbyteArray arr = (*env)->NewByteArray(env, size);
    (*env)->SetByteArrayRegion(env,arr,0,size, (jbyte*)fbb);

    freefbb(fbb);

    return arr;
}

(3)app java層接收到資料後對framebuffer進行bitmap的封裝,並通過socket傳送給PC的server端,然後實時顯示。關鍵程式碼如下:
	class MainThread extends Thread{
		private byte byteBuffer[] = new byte[1024];
		private OutputStream outsocket = null;	
		private int i =0;

		public void run(){
			Log.i(TAG,"Send data to PC thread created."); 
			
			while(isStartService){
				if(isConnectServerBoolean){
					if(isSendScreen){
						try {
							long totalStartTime = System.currentTimeMillis(); 
							
							byte[] bt = writeImageOutputStream();
							long startTime=System.currentTimeMillis();  

							Socket tempSocket = null;
							try {
								tempSocket = new Socket(serverUrl, serverPort);
							} catch (UnknownHostException e) {
								e.printStackTrace();
							} catch (IOException e) {
								e.printStackTrace();
							}
							outsocket = tempSocket.getOutputStream();

							String msg=java.net.URLEncoder.encode("PHONESCREEN|"+pUsername+"|","utf-8");
							byte[] buffer= msg.getBytes();
							outsocket.write(buffer);

							ByteArrayInputStream inputstream = new ByteArrayInputStream(bt);
							int amount;
							while ((amount = inputstream.read(byteBuffer)) != -1) {
								outsocket.write(byteBuffer, 0, amount);
							}
							tempSocket.close(); 
							long endTime=System.currentTimeMillis(); 
							Log.i(TAG,"Wifi send raw data £∫ "+(endTime-startTime)+"ms"); 

							bt = null;
							inputstream = null;
							outsocket = null;
							System.gc();
							Log.i(TAG,"Total time £∫ "+(endTime-totalStartTime)+"ms"); 
							Log.i(TAG,"*********************************************************"); 

						} catch (IOException e) {
							Log.i(TAG,"IOException occured."); 
						}

					}
				}
			}
		}
5.目前存在的問題

說是實時,其實還差的元,如果裝置的螢幕解析度很大的,那麼每次傳送的資料量是比較大的。可以有以下兩種改進的方法:

      (1) 可以藉助PC上的VNC遠端桌面的思路,每次只傳送變化的資料,這樣可以大大節省頻寬。在螢幕內容變化不是太大的情況下可以達到區域網內的手機螢幕實時顯示。

      (2)利用jpeg壓縮的C library, 在local service擷取到framebuffer資料後快速進行壓縮,這樣在傳輸給app的client端,然後app的client不再對framebuffer資料進行bitmap封裝(這個過程在java層是比較耗時的),然後立即傳送給PC端的server端,這樣會進一步加快screenshot的傳輸速度。

github原始碼:https://github.com/zhouguangfu09/Screenshot-non-root-