Android平臺之不預覽獲取照相機預覽數據幀及精確時間截

  在android平臺上要獲取預覽數據幀是一件極其容易的事兒,但要獲取每幀數據對應的時間截並不那麼容易,網絡上關於這方面的資料也比較少。之因此要獲取時間截,是由於某些狀況下須要加入精確時間軸才能解決問題,若是本身給獲取到的時間截打上時間截,則一定引入不少偏差,文檔主要以理論爲主,我想做爲一名合格的程序員,有了一個想法,則必定會有辦法去編碼實現的。html

  由於項目須要,查找了大量的資料,發現網絡上關於獲取預覽數據的資料都是經過實現PreviewCallback接口來獲取。這種方法能獲取到照相機的預覽數據,可是系統不提供時間截服務,本身打上時間截,可能會致使預覽數據幀發生時間截偏移。具體分析來講,若是你實現了PreviewCallback接口,調用setPreviewDisplay使用一個SurfaceHolder來顯示預覽數據,而且在onPreviewFrame回調函數中獲取到了幀數據,可是你不能獲取該幀產生時的時間截。你須要爲他手工編碼打上時間截,或許你就是以程序運行到那行代碼時刻的時間截,但在幀生成到調用回調onPreviewFrame,再到運行該行代碼,已經消耗了一些時間,若是你的預覽頻率設置不當的話,會使得消耗是時間是你設定的預覽幀間隔的幾倍,這樣偏差可能就致使了錯誤的時間截。android

  使用PreviewCallback和SurfaceView來獲取預覽數據的方法,還有很大的問題就是你必須把預覽獲得的數據顯示出來,才能在onPreviewFrame回調函數中獲取到數據。官方API是這麼解釋的,但這一點在2.3及之前版本的android中並不必定成立,由於我已經在2.3,2.2的系統中測試關閉輸出,仍然能在onPreviewFrame中獲取數據,但一樣的方法在4.0以上的系統中則不能夠。獲取你可能會問,爲何要關閉預覽輸出?這個問題可能會有各類答案,但很明顯的是它能夠明顯減小系統資源的消耗,從而可使得照相機Camera可以以更大的預覽頻率輸出。那麼,怎麼樣才能使得高版本的android在不顯示預覽的狀況下也能得到預覽數據呢?這種狀況下,一個叫SurfaceTexture的類登場了。程序員

  SurfaceTexture是直接繼承自Object類, 最低版本api 11,關於SurfaceTexture的介紹,官方是這麼介紹的——"A Surface created from a SurfaceTexture can be used as an output destination for the android.hardware.camera2, MediaCodec, MediaPlayer, and Allocation APIs."和"A SurfaceTexture may also be used in place of a SurfaceHolder when specifying the output destination of the older Camera API. Doing so will cause all the frames from the image stream to be sent to the SurfaceTexture object rather than to the device's display."。也就是說SurfaceTexture能夠做爲視頻或圖像流的輸出載體。說明一下,由於android5.0的推出,要廢棄Camera類,使用Camera2來替代,因此說"older Camera API"。總之,若是你使用SurfaceTexure來做爲Camera的輸出載體(調用Camera的setPreviewTexture便可把生成的SurfaceTexture對象設置了輸出載體),那麼就能夠把SurfaceTexture做爲預覽數據緩存地方,而不用再屏幕上顯示出來,顯然你要爲設置一個足夠大的緩存區域。有了SurfaceTexture,那麼接下來的工做就變得容易多了,下面說說本文提到的另外一個重點就是獲取到精確的時間截。canvas

  查閱SurfaceTexture類API就會發現它還提供了一個getTimeStamp()函數,官方介紹"Retrieve the timestamp associated with the texture image set by the most recent call to updateTexImage.",也就是說它能夠得到SurfaceTexture最新數據幀的時間截,但在這以前須要調用updateTexImage()更新數據,另外getTimeStamp返回值的單位是納秒。而updateTexImage()的調用對SurfaceTexture有要求,必須把SurfaceTexture設置爲 GL_TEXTURE_EXTERNAL_OES texture 類型。能夠這樣編寫代碼:api

surfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);

使用SurfaceTexture獲取預覽數據也是要實現PreviewCallback,方法同前文提到的PreviewCallback獲取預覽數據,不一樣的是在startPreview以前,再也不調用setPreviewDisplay,而是使用Camera的setPreviewTexture。緩存

 1 int version = android.os.Build.VERSION.SDK_INT;  2 if (version >= OSSURPORTFORSURFACETEXTURE) {  3     try {  4  camera.setPreviewTexture(surfaceTexture);  5         int buffersize = WIDTH_COLLECT * HEIGHT_COLLECT  6                 * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8;  7         previewBuffer = new byte[buffersize];  8  camera.addCallbackBuffer(previewBuffer);  9         camera.setPreviewCallbackWithBuffer(this); 10  camera.startPreview(); 11         isView = true; 12     } catch (IOException e) { 13  camera.release(); 14         camera = null; 15  e.printStackTrace(); 16  } 17 } else { 18  ...... 19 }

 另外還要主要的就是要記得onPreviewFrame回調函數中添加addCallbackBuffer調用,否則緩存不會自動更新,就不能獲取到後續的數據幀;要獲取精確時間截(這裏說精確,是由於這個時間截是系統在數據發送到SurfaceTexture時設置的,很是接近預覽數據生成的時間,要遠比手工在onPreviewFrame中打上數據的時間截準確),還要調用updateTexImage()。能夠像下面這樣編寫程序:網絡

1 long timestampx; 2 if (osversion >= OSSURPORTFORSURFACETEXTURE) { 3  surfaceTexture.updateTexImage(); 4     timestampx = surfaceTexture.getTimestamp()/1000000; 5  camera.addCallbackBuffer(previewBuffer); 6 }

 

上述代碼是寫在onPreviewFrame回調函數中的,有一個值得注意的地方是不要在onPreviewFrame中作耗時的工做,由於那麼很可能會致使丟掉一些預覽數據幀。函數

  經過上面的方法,已經能夠在不顯示預覽的狀況下獲取到數據幀,並打上極爲精確的生成時間截,這對於須要精確計算時間的程序來講是很是有用的。固然是用SurfaceTexture也能夠將預覽圖像顯示出來,你能夠開一個線程專門來作這件事,而不是在onPreviewFrame中完成。下面提供一段顯示預覽圖像的參考代碼:測試

 1 try {  2     YuvImage image = new YuvImage(data, ImageFormat.NV21,  3             WIDTH_COLLECT, HEIGHT_COLLECT, null);  4     if (image != null) {  5         ByteArrayOutputStream stream = new ByteArrayOutputStream();  6         image.compressToJpeg(new Rect(0, 0, WIDTH_COLLECT,  7                 HEIGHT_COLLECT), 100, stream);  8         Bitmap bm = BitmapFactory.decodeByteArray(  9                 stream.toByteArray(), 0, stream.size()); 10  stream.close(); 11         Canvas canvas = previewHolder.lockCanvas(); 12         canvas.drawBitmap(bm, 0, 0, null); 13  previewHolder.unlockCanvasAndPost(canvas); 14  } 15 } catch (Exception e) { 16     // TODO: handle exception
17 }

 

previewHolder就是要顯示預覽數據的SurfaceView的SurfaceHolder,固然你要能夠加上synchronized同步機制。ui

  Demo就沒有上傳了,若是有什麼問題能夠直接留言討論。雖然寫得比較水,歡迎複製粘貼,轉載請帶上原文地址:http://www.cnblogs.com/HackingProgramer/p/4062119.html