天天看點

談談Android中的SurfaceTexture

原文位址為: 談談Android中的SurfaceTexture

2015.7.2更新

由于很多人要代碼,我把代碼下載下傳連結放在這裡了。不過還是要說一下,surfaceTexture和OpenGL ES結合才能發揮出它最大的效果,我這種寫法隻是我自己的想法,還有很多種解決方法,如果大家學習一下OpenGL ES,會發現更多有意思的東西。

SurfaceTexture是從Android3.0(API 11)加入的一個新類。這個類跟SurfaceView很像,可以從camera preview或者video decode裡面擷取圖像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收圖像流之後,不需要顯示出來。有做過Android camera開發的人都知道,比較頭疼的一個問題就是,從camera讀取到的預覽(preview)圖像流一定要輸出到一個可見的(Visible)SurfaceView上,然後通過Camera.PreviewCallback的public void onPreviewFrame(byte[] data, Camera camera)函數來獲得圖像幀資料的拷貝。這就存在一個問題,比如我希望隐藏攝像頭的預覽圖像或者對每一幀進行一些處理再顯示到手機顯示屏上,那麼在Android3.0之前是沒有辦法做到的,或者說你需要用一些小技巧,比如用其他控件把SurfaceView給擋住,注意這個顯示原始camera圖像流的SurfaceView其實是依然存在的,也就是說被擋住的SurfaceView依然在接收從camera傳過來的圖像,而且一直按照一定幀率去重新整理,這是消耗cpu的,而且如果一些參數設定的不恰當,後面隐藏的SurfaceView有可能會露出來,是以這些小技巧并不是好辦法。但是,有了SurfaceTexture之後,就好辦多了,因為SurfaceTexture不需要顯示到螢幕上,是以我們可以用SurfaceTexture接收來自camera的圖像流,然後從SurfaceTexture中取得圖像幀的拷貝進行處理,處理完畢後再送給另一個SurfaceView用于顯示即可。

在我的應用場景中,我需要從camera讀取圖像流,然後對其處理,最後将處理結果顯示到手機螢幕上。是以我寫了一個GameDisplay類用于處理以上所有事務,而在MainActivity中,隻需要建立一個GameDisplay類的執行個體并且初始化即可。

1 package com.song.camgame;
  2 
  3 import java.io.IOException;
  4 import java.util.List;
  5 import java.util.Timer;
  6 import java.util.TimerTask;
  7 
  8 import android.app.Activity;
  9 import android.content.Context;
 10 import android.graphics.Bitmap;
 11 import android.graphics.Canvas;
 12 import android.graphics.ImageFormat;
 13 import android.graphics.Rect;
 14 import android.graphics.SurfaceTexture;
 15 import android.hardware.Camera;
 16 import android.hardware.Camera.Size;
 17 import android.util.Log;
 18 import android.view.SurfaceHolder;
 19 import android.view.SurfaceView;
 20 
 21 public class GameDisplay extends SurfaceView implements SurfaceHolder.Callback,
 22 Camera.PreviewCallback{
 23     public final static String TAG="GameDisplay";
 24     
 25     private static final int MAGIC_TEXTURE_ID = 10;
 26     public static final int DEFAULT_WIDTH=800;
 27     public static final int DEFAULT_HEIGHT=480;
 28     public static final int BLUR = 0;
 29     public static final int CLEAR = BLUR + 1;
 30     //public static final int PAUSE = PLAY + 1;
 31     //public static final int EXIT = PAUSE + 1;
 32     public SurfaceHolder gHolder;
 33     public  SurfaceTexture gSurfaceTexture;
 34     public Camera gCamera;
 35     public byte gBuffer[];
 36     public int textureBuffer[];
 37     public ProcessThread gProcessThread;
 38     private int bufferSize;
 39     private Camera.Parameters parameters;
 40     public int previewWidth, previewHeight;
 41     public int screenWidth, screenHeight;
 42     public Bitmap gBitmap;
 43     private Rect gRect;
 44     // timer
 45     private Timer sampleTimer;
 46     private TimerTask sampleTask;
 47     
 48     public GameDisplay(Context context,int screenWidth,int screenHeight) {
 49         super(context);
 50         gHolder=this.getHolder();
 51         gHolder.addCallback(this);
 52         gHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
 53         gSurfaceTexture=new SurfaceTexture(MAGIC_TEXTURE_ID);
 54         this.screenWidth=screenWidth;
 55         this.screenHeight=screenHeight;
 56         gRect=new Rect(0,0,screenWidth,screenHeight);
 57         Log.v(TAG, "GameDisplay initialization completed");
 58     }
 59 
 60     @Override
 61     public void surfaceChanged(SurfaceHolder holder, int format, int width,
 62             int height) {
 63         Log.v(TAG, "GameDisplay surfaceChanged");
 64         parameters = gCamera.getParameters();    
 65         List<Size> preSize = parameters.getSupportedPreviewSizes();
 66         previewWidth = preSize.get(0).width;
 67         previewHeight = preSize.get(0).height;
 68         for (int i = 1; i < preSize.size(); i++) {
 69             double similarity = Math
 70                     .abs(((double) preSize.get(i).height / screenHeight)
 71                             - ((double) preSize.get(i).width / screenWidth));
 72             if (similarity < Math.abs(((double) previewHeight / screenHeight)
 73                                     - ((double) previewWidth / screenWidth))) {
 74                 previewWidth = preSize.get(i).width;
 75                 previewHeight = preSize.get(i).height;
 76             }
 77         }
 78         gBitmap= Bitmap.createBitmap(previewWidth, previewHeight, Bitmap.Config.ARGB_8888);
 79         parameters.setPreviewSize(previewWidth, previewHeight);
 80         gCamera.setParameters(parameters);
 81         bufferSize = previewWidth * previewHeight;
 82         textureBuffer=new int[bufferSize];
 83         bufferSize  = bufferSize * ImageFormat.getBitsPerPixel(parameters.getPreviewFormat()) / 8;
 84         gBuffer = new byte[bufferSize];
 85         gCamera.addCallbackBuffer(gBuffer);
 86         gCamera.setPreviewCallbackWithBuffer(this);
 87         gCamera.startPreview();
 88         //gProcessThread = new ProcessThread(surfaceView,handler,null,previewWidth,previewHeight);
 89         //processThread.start();
 90     }
 91 
 92     @Override
 93     public void surfaceCreated(SurfaceHolder holder) {
 94         Log.v(TAG, "GameDisplay surfaceCreated");
 95         if (gCamera == null) {
 96             gCamera = Camera.open();
 97         }
 98         gCamera.setPreviewTexture(gSurfaceTexture);
 99         //sampleStart();
100     }
101 
102     @Override
103     public void surfaceDestroyed(SurfaceHolder holder) {
104         Log.v(TAG, "GameDisplay surfaceDestroyed");
105         //gProcessThread.isRunning=false;
106         //sampleTimer.cancel();
107         //sampleTimer = null;
108         //sampleTask.cancel();
109         //sampleTask = null;
110         gCamera.stopPreview();
111         gCamera.release();
112     }
113 
114     @Override
115     public void onPreviewFrame(byte[] data, Camera camera) {
116         Log.v(TAG, "GameDisplay onPreviewFrame");
117         //gProcessThread.raw_data=data;    
118         camera.addCallbackBuffer(gBuffer);
119         for(int i=0;i<textureBuffer.length;i++)
120             textureBuffer[i]=0xff000000|data[i];
121         gBitmap.setPixels(textureBuffer, 0, previewWidth, 0, 0, previewWidth, previewHeight);
122         synchronized (gHolder)
123         {        
124             Canvas canvas = this.getHolder().lockCanvas();
125             canvas.drawBitmap(gBitmap, null,gRect, null);
126             //canvas.drawBitmap(textureBuffer, 0, screenWidth, 0, 0, screenWidth, screenHeight, false, null);
127             this.getHolder().unlockCanvasAndPost(canvas);
128         }
129         
130     }
131     
132     public void sampleStart() {
133         Log.v(TAG, "GameDisplay sampleStart");
134         sampleTimer = new Timer(false);
135         sampleTask = new TimerTask() {
136             @Override
137             public void run() {
138                 gProcessThread.timer=true;
139             }
140         };
141         sampleTimer.schedule(sampleTask,0, 80);
142     }
143 }      

以上程式中115-130行是最重要的部分,data是從SurfaceTexture獲得的camera圖像幀的拷貝,119-121行對其進行了簡單的處理,122-128行将處理過的圖像資料傳遞給負責顯示的SurfaceView并顯示出來。

MainActivity對GameDisplay的調用如下:

1 //聲明
 2 private GameDisplay gameDisplay;
 3 //初始化
 4 gameDisplay.setVisibility(SurfaceView.VISIBLE);
 5 DisplayMetrics dm = getResources().getDisplayMetrics();
 6 screenWidth = dm.widthPixels;
 7 screenHeight = dm.heightPixels;
 8 gameDisplay= new GameDisplay(this,dm.widthPixels,dm.heightPixels);
 9 //加入到目前activity的layout中
10 FrameLayout root = (FrameLayout) findViewById(R.id.root);
11 root.addView(gameDisplay,0);      

轉載請注明本文位址: 談談Android中的SurfaceTexture