首先我得感谢我的大师兄以及我的师姐,还有网上的一个朋友,没有他们的帮忙我也做不出来。
进入正题吧,首先在android2.3.3/development里面新建一个文件夹(比如说test),然后在test里面建两个文件,Android.mk与screencap.cpp(截屏的一些基础知识请看我的博客 浅谈android截屏http://my.oschina.net/JumpLong/blog/75556
)
/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <utils/Log.h> #include <errno.h> #include <netdb.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IServiceManager.h> #include <binder/IMemory.h> #include <surfaceflinger/ISurfaceComposer.h> #include <SkImageEncoder.h> #include <SkBitmap.h> #include <SkStream.h> using namespace android; long getCurrentTime() { struct timeval tv; gettimeofday(&tv,NULL); return tv.tv_sec * 1000 + tv.tv_usec / 1000 ; } long SocketPre,SocketAft,SocketTime; long CapPre,CapAft,CapTime; long SendPre,SendAft,SendTime; long EncodePre,EncodeAft,EncodeTime; int SetSockfd(char* strip,char* strport,int* psockfd) { SocketPre = getCurrentTime(); struct sockaddr_in server_addr; in_addr_t inaddrip; int portnumber; if((inaddrip=inet_addr(strip))==INADDR_NONE) { fprintf(stderr,"ip Error!\n"); LOGD("ip error!"); return -1; } if((portnumber=atoi(strport))<0) { fprintf(stderr,"portnumber Error\n"); LOGD("port error!"); return -1; } /* 客户程序开始建立 sockfd描述符 */ if((*psockfd=socket(AF_INET,SOCK_STREAM,0))==-1) { fprintf(stderr,"Socket Error:%s\a\n",strerror(errno)); LOGD("socketcreate error!"); return -1; } /* 客户程序填充服务端的资料 */ bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family=AF_INET; server_addr.sin_port=htons(portnumber); server_addr.sin_addr.s_addr=inaddrip; /* 客户程序发起连接请求 */ if(connect(*psockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1) { fprintf(stderr,"Connect Error:%s\a\n",strerror(errno)); LOGD("connect error!"); return -1; } SocketAft = getCurrentTime(); SocketTime = SocketAft - SocketPre; return 0; } char* CapScreen(int* len) { const String16 name("SurfaceFlinger"); sp<ISurfaceComposer> composer; getService(name, &composer); sp<IMemoryHeap> heap; uint32_t w, h; PixelFormat f; EncodePre = getCurrentTime(); status_t err = composer->captureScreen(0, &heap, &w, &h, &f, 0, 0); if (err != NO_ERROR) { fprintf(stderr, "screen capture failed: %s\n", strerror(-err)); return NULL; } EncodeAft = getCurrentTime(); EncodeTime = EncodeAft - EncodePre; printf("screen capture success: w=%u, h=%u, pixels=%p,heapsize = %zu,heapsize = %zx\n",w, h, heap->getBase() ,heap->getSize(),heap->getSize()); SkBitmap b; b.setConfig(SkBitmap::kARGB_8888_Config, w, h); b.setPixels(heap->getBase()); printf("size = %ld\n",sizeof(b)); CapPre = getCurrentTime(); SkDynamicMemoryWStream stream; SkImageEncoder::EncodeStream(&stream, b,SkImageEncoder::kJPEG_Type,SkImageEncoder::kDefaultQuality); *len=stream.getOffset(); char* buffer=new char[*len]; memcpy(buffer,stream.getStream(),*len); CapAft = getCurrentTime(); CapTime = CapAft - CapPre; return buffer; } int SendScreen(int sockfd) { SendPre = getCurrentTime(); int jpglong; char* jpgbuffer=CapScreen(&jpglong); if(jpgbuffer==NULL){ printf("CapScreen failed!\n"); return -1; } //加jpg头和长度 printf("screen size is %d byte\n",jpglong); char* pbuffer=new char[jpglong+6]; pbuffer[0]='\r'; pbuffer[1]='\n'; memcpy(pbuffer+2,&jpglong,sizeof(int)); memcpy(pbuffer+6,jpgbuffer,jpglong); delete jpgbuffer; if(send(sockfd,pbuffer,jpglong+6,0)<0) { LOGD("send error"); printf("send error closed!\n"); delete pbuffer; return -1; } SendAft = getCurrentTime(); SendTime = SendAft - SendPre; delete pbuffer; return 0; } int main(int argc, char** argv) { if(argc != 3) { fprintf(stderr,"Usage:%s ip portnumber\a\n",argv[0]); exit(1); } int sockfd; if(SetSockfd(argv[1],argv[2],&sockfd)==-1) { fprintf(stderr,"setsockfd failed!\n"); exit(1); } while(true) { long pre = getCurrentTime(); if(SendScreen(sockfd)!=0){ printf("SendScreen failed!\n"); close(sockfd); return -1; } long aft = getCurrentTime(); long useTime = aft - pre; printf("total %ld,setsocket %ld,cap %ld,send %ld,encode %ld\n",useTime,SocketTime,CapTime,SendTime,EncodeTime); } close(sockfd); return 0; }
这里面主要是调用了screencap那个文件夹里面的screencap.cpp里面的截屏方法,然后加上了socket传输,getCurrentTime只是我用来测试截屏,传输,解码的所用时间的。这个程序运行的话需要三个参数,第一个参数就是执行嘛,第二个就是服务器的IP,第三个就是服务器的端口号。等下再说。。。。
大家都知道要想图片流畅,那么每秒显示的图片数量得有24幅,但是这个目前只能达到5幅,因为它截屏大概是60ms,但是编码得需要大概120ms,也就是平均得要0.2s,如果做成多线程的话,也就是说利用生产者与消费者的模式,生产者负责capture,消费者负责encode,那样的话至少每秒会产生10幅图片(尚未解决,求高手指点)
Android.mk
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ screencap.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libbinder \ libskia \ libui \ libsurfaceflinger_client LOCAL_MODULE:= whilecap LOCAL_MODULE_TAGS := tests LOCAL_C_INCLUDES += \ external/skia/include/core \ external/skia/include/effects \ external/skia/include/images \ external/skia/src/ports \ external/skia/include/utils include $(BUILD_EXECUTABLE)有了这两个文件后,进入终端,转到test目录,然后mm,没错的话就会出现
Install: out/target/product/generic/system/bin/whilecap
然后将这个whilecap push到已经root的手机里面,注意不要随便放在一个文件里面,sdcard就不行,可以放在system里面,但是在push到system的时候会提示read-only system,这个时候就需要一下几步:adb shell ->su ->mount -o rw,remount -t ext3 /dev/block/mmcblk1p21 /system,然后再次adb push whilecap /system (当然这是你的whilecap已经被你移到home文件夹的情况下)。
现在假设服务器程序已经安装了,那么你怎么去执行呢?adb shell -> /system/whilecap 192.168.0.*** **** 这就是前面说的三个参数,第二个是IP,第三个是port。
当然我们想的不是每次都要去用命令行去执行,多麻烦,于是有了下一步的使用java去调用命令行语句。
public static int RunRootCommand(String paramString) { int i = 0; Process localProcess = null; try { localProcess = Runtime.getRuntime().exec("su"); OutputStreamWriter localOutputStreamWriter = new OutputStreamWriter(localProcess.getOutputStream(), "UTF-8"); // localOutputStreamWriter.write("export LD_LIBRARY_PATH=endorb:/systemb\n"); //localOutputStreamWriter.flush(); localOutputStreamWriter.write(paramString + "\n"); localOutputStreamWriter.flush(); localOutputStreamWriter.write("exit\n"); localOutputStreamWriter.flush(); localOutputStreamWriter.close(); localProcess.waitFor(); int j = localProcess.exitValue(); if (j == 0) i = 1; try { localProcess.destroy(); return i; } catch (Exception localException4) { System.out.println(localException4.getMessage()); while (true) localException4.printStackTrace(); } } catch (Exception localException2) { System.out.println(localException2.getMessage()); while (true) { localException2.printStackTrace(); try { localProcess.destroy(); } catch (Exception localException3) { localException3.printStackTrace(); } } } finally { } }
新建一个android程序,在mainActivity中调用此函数,传送的字符串参数就是我们刚才使用的三个参数合在一起的结果,比如
RunRootCommand("/system/whilecap 192.168.0.100 7787");
这样,客户端的程序就完成了。
下面是服务器端的程序,分为两个,一个是android版本的,一个是java project。
android版本:
package com.example.jumplong; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.Toast; public class TCA extends Activity { /** Called when the activity is first created. */ Boolean state = false; Bitmap bitmap = null; ImageView iv = null; Thread severThread; static Handler mHandler; long pre,aft; int photo; static ServerSocket ss = null; static Socket cs = null; static InputStream in = null; static DataInputStream din = null; static CriticQueue cq; static imgReceiver ir; static imgShower is; static Thread tir; static Thread tis; static AtomicInteger imgCount; @Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return true; } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { switch (item.getItemId()) { case R.id.start: Log.e("tc"," current Time ��"+new Date( System.currentTimeMillis())); start(); return true; case R.id.stop: stop(); return true; default: return super.onOptionsItemSelected(item); } } private void start() { Log.e("tc", "enter the TCLCA start()"); state = true; iv = (ImageView) findViewById(R.id.showPhoto); //bitmap severThread = new Thread(new ServeThread()," ServeThread() "); severThread.start(); } private void stop() { // TODO Auto-generated method stub state = false; try { ss.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } aft = System.currentTimeMillis(); Toast.makeText(getApplicationContext(), "photo " + photo + " time " + ( aft - pre ), Toast.LENGTH_SHORT).show(); // android.os.Process.killProcess((int)Thread.getId()); finish(); // ActivityManager manager = // (ActivityManager)getSystemService(ACTIVITY_SERVICE); // manager.killBackgroundProcesses(getPackageName()); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 去除标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); } class ServeThread implements Runnable { public ServeThread() { mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 1: iv.setImageBitmap(bitmap); // ��ImageView����ʾBitmap Log.e("tc", "setImageBitmap sucess"); break; default: break; } super.handleMessage(msg); } }; } @Override public void run() { try { ss = new ServerSocket(12345); } catch (IOException e) { Log.e("tc", "get the ServerSocket(54321) failed "); } imgCount = new AtomicInteger();// 0 while (true) { try { cs = ss.accept(); pre = System.currentTimeMillis(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { in = cs.getInputStream(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } din = new DataInputStream(in); cq = new CriticQueue(); ir = new imgReceiver(cq); is = new imgShower(cq); tir = new Thread(ir, "thread-p"); tis = new Thread(is, "thread-c"); tir.start(); tis.start(); } }//run end }// class ServeThread end class imgInfo { private byte[] imglocation; public void setimglocation(byte[] imgloc) { this.imglocation = imgloc; } } class CriticQueue { ArrayList<imgInfo> imgInfos = new ArrayList<imgInfo>(); public void produce(imgInfo imginfo) { if (imgInfos.add(imginfo)){ imgCount.incrementAndGet(); } }// produce end public synchronized void consume() throws IOException { while (imgInfos.size() == 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } imgInfo img = imgInfos.get(0); imgInfos.remove(0); // first stage bitmap = BitmapFactory.decodeByteArray(img.imglocation, 0,img.imglocation.length); photo++; Message amessage = new Message(); amessage.what = 1; mHandler.sendMessage(amessage); img = null; imgCount.decrementAndGet(); } } class imgReceiver extends Thread { private CriticQueue imgReceiver_crique; boolean streamIsEnd = false; imgReceiver(CriticQueue q) { this.imgReceiver_crique = q; } public void run() { try { // search for mark of the image int s0 = 0; int s1 = 0; int imgsize = 0; int inter = 0; byte[] len = new byte[4]; while ( !streamIsEnd ) { // search for 13 s0 = din.read(); while (s0 == -1) { s0 = din.read(); } while (s0 != 13) { s0 = din.read(); } if (din.read() == 10) { try { s1 = din.read(); while ((s1 != -1) && (inter < 4)) { len[inter] = (byte) s1; inter++; s1 = din.read(); } if (inter == 4) { imgsize = (len[0] & 0xff) | ((len[1] << 8) & 0xff00) | ((len[2] << 24) >>> 8) | (len[3] << 24); inter=0; }else{ streamIsEnd = true; } if (!streamIsEnd) { byte[] img = new byte[imgsize]; int readonce = 0; int totalread = 0; img[0] = (byte) s1; totalread++; while (totalread != imgsize) { readonce = din.read(img, totalread, imgsize - totalread); if (readonce == -1) { streamIsEnd = true; break; } totalread += readonce; if (totalread == imgsize) { break; } } if (!streamIsEnd) { imgInfo message = new imgInfo(); message.setimglocation(img); imgReceiver_crique.produce(message); } else { din.close(); in.close(); cs.close(); while (imgCount.get() > 0) { } streamIsEnd = true; } } else { din.close(); in.close(); cs.close(); while (imgCount.get() > 0) { } streamIsEnd = true; } } catch (IOException e) { e.printStackTrace(); } }// if 10 end }// while( !streamIsEnd ) end } catch (SocketException e) { try { din.close(); in.close(); cs.close(); while (imgCount.get() > 0) { } } catch (IOException e1) { e1.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } }// run end }// imgReceiver end class imgShower extends Thread { private CriticQueue imgShower_crique = null; imgShower(CriticQueue q) { this.imgShower_crique = q; } public void run() { while (true) { if (state == true) { while (imgCount.get() < 1) {} try { imgShower_crique.consume(); } catch (IOException e) { e.printStackTrace(); } } } } }// imgShower end }// class TCA end
main.XML
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ImageView android:id="@+id/showPhoto" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
最后添加权限
<uses-permission android:name ="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name ="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name ="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.INTERNET" />
然后java project版本:
package com.jump.screencapclient; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; public class test{ static JFrame frame = new JFrame(); final static JFrame win = new JFrame("JumpLong"); ; static Graphics g; Image img; static long pre ; long aft; static int photo; static Boolean state = false; static ServeThread severThread; static ServerSocket ss = null; static Socket cs = null; static InputStream in = null; static DataInputStream din = null; static CriticQueue cq; static imgReceiver ir; static imgShower is; static Thread tir; static Thread tis; static AtomicInteger imgCount; static BufferedImage image; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Container contentPane; win.setSize(480,900); win.setVisible(true); win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//關閉 g = win.getGraphics(); severThread = new ServeThread("tcl capture"); Thread t = new Thread(severThread); t.start(); } public static class ServeThread implements Runnable { String name; public ServeThread(String name){ this.name = name; } @Override public void run() { System.out.println("run"); try { ss = new ServerSocket(12345); System.out.println("open the socket "); } catch (IOException e) { System.out.println("get the ServerSocket(54321) failed "); } imgCount = new AtomicInteger();// 0 while (true) { try { cs = ss.accept(); pre = System.currentTimeMillis(); System.out.println("accept "); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { in = cs.getInputStream(); System.out.println("get the inputstream "); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } din = new DataInputStream(in); System.out.println("after din "); cq = new CriticQueue(); System.out.println("after cq"); ir = new imgReceiver(cq); System.out.println("after ir "); is = new imgShower(cq); System.out.println("after is "); tir = new Thread(ir, "thread-p"); System.out.println("after tir "); tis = new Thread(is, "thread-c"); System.out.println("after tis "); tir.start(); System.out.println("after tir "); tis.start(); System.out.println("after tis "); } }//run end }// class ServeThread end public static class imgInfo { private byte[] imglocation; public void setimglocation(byte[] imgloc) { this.imglocation = imgloc; } } public static class CriticQueue { ArrayList<imgInfo> imgInfos = new ArrayList<imgInfo>(); public void produce(imgInfo imginfo) { if (imgInfos.add(imginfo)){ imgCount.incrementAndGet(); } }// produce end public synchronized void consume() throws IOException { System.out.println("consume"); while (imgInfos.size() == 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } imgInfo img = imgInfos.get(0); imgInfos.remove(0); System.out.print("remove "); // first stage ByteArrayInputStream in = new ByteArrayInputStream(img.imglocation); //将b作为输入流; image = ImageIO.read(in); //将in作为输入流,读取图片存入image中,而这里in可以为ByteArrayInputStream(); System.out.print("image " + photo++ +"time " + ( System.currentTimeMillis()-pre )); g.drawImage(image,0,0, win); // JFrame frame = new JFrame(); // frame.invalidate(); // JLabel label = new JLabel(new ImageIcon(image)); // System.out.print("show the image "); // frame.getContentPane().add(label, BorderLayout.CENTER); // frame.pack(); // frame.setVisible(true); // //�رմ���--�˳����� // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); img = null; imgCount.decrementAndGet(); } } public static class imgReceiver extends Thread { private CriticQueue imgReceiver_crique; boolean streamIsEnd = false; imgReceiver(CriticQueue q) { this.imgReceiver_crique = q; } public void run() { try { // search for mark of the image int s0 = 0; int s1 = 0; int imgsize = 0; int inter = 0; byte[] len = new byte[4]; while ( !streamIsEnd ) { // search for 13 s0 = din.read(); System.out.println("din.read() "); while (s0 == -1) { s0 = din.read(); System.out.println("s0 == -1"); } while (s0 != 13) { s0 = din.read(); System.out.println("s0 != 13 "); } if (din.read() == 10) { try { System.out.println("din.read() == 10"); s1 = din.read(); while ((s1 != -1) && (inter < 4)) { len[inter] = (byte) s1; inter++; s1 = din.read(); } if (inter == 4) { imgsize = (len[0] & 0xff) | ((len[1] << 8) & 0xff00) | ((len[2] << 24) >>> 8) | (len[3] << 24); inter=0; }else{ streamIsEnd = true; System.out.println("streamisend = true "); } if (!streamIsEnd) { System.out.println("!streamisend"); byte[] img = new byte[imgsize]; int readonce = 0; int totalread = 0; img[0] = (byte) s1; totalread++; System.out.println("titalread = " + totalread); while (totalread != imgsize) { readonce = din.read(img, totalread, imgsize - totalread); if (readonce == -1) { streamIsEnd = true; break; } totalread += readonce; if (totalread == imgsize) { break; } } if (!streamIsEnd) { imgInfo message = new imgInfo(); message.setimglocation(img); imgReceiver_crique.produce(message); } else { din.close(); in.close(); cs.close(); while (imgCount.get() > 0) { } streamIsEnd = true; System.out.println("streamisend is true"); } } else { din.close(); in.close(); cs.close(); while (imgCount.get() > 0) { System.out.println("imgcount.get > 0 "); } streamIsEnd = true; } } catch (IOException e) { e.printStackTrace(); } }// if 10 end }// while( !streamIsEnd ) end } catch (SocketException e) { try { din.close(); in.close(); cs.close(); while (imgCount.get() > 0) { } } catch (IOException e1) { e1.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } }// run end }// imgReceiver end public static class imgShower extends Thread { private CriticQueue imgShower_crique = null; imgShower(CriticQueue q) { this.imgShower_crique = q; } public void run() { while (true) { System.out.println("imgshower run while(true) "); if (!state) { System.out.println("state == true "); while (imgCount.get() < 1) {} try { System.out.println("imgcount.get() < 1 "); System.out.println("imgshower try function"); imgShower_crique.consume(); } catch (IOException e) { e.printStackTrace(); } } } } }// imgShower end }
现在客户端与服务器端都写好,运行的步骤就是:
1:打开服务器程序:java project的话就直接运行,android project的话就是运行后点击手机的菜单按钮,就会出现两个按钮,点击左边的开启服务器端(端口号目前设置的是12345,假设服务器的IP地址是192.168.0.100)
2:运行客户端程序:你可以在android程序中添加一个editview来接受我们要传给RunRootCommand函数的参数,这个自己去写就是了,最终达到的效果就是如下
RunRootCommand("/system/whilecap 192.168.0.100 12345");
3:结束:点击服务器端的关闭服务器端,此时不再传送图片,但是客户端程序还在运行,需要去任务管理器中强行结束。因为此程序还没有优化,只是一个雏形。
到此,android的无线传屏就搞定了。
最后,还是希望大家帮帮忙,怎么去提升性能,怎么尽可能地增加每秒截取(截取与编码)的图片。