歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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

  在Android平台上要獲取預覽數據幀是一件極其容易的事兒,但要獲取每幀數據對應的時間截並不那麼容易,網絡上關於這方面的資料也比較少。之所以要獲取時間截,是因為某些情況下需要加入精確時間軸才能解決問題,如果自己給獲取到的時間截打上時間截,則必定引入很多誤差,文檔主要以理論為主,我想作為一名合格的程序員,有了一個想法,則一定會有辦法去編碼實現的。

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

  使用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,那麼接下來的工作就變得容易多了,下面說說本文提到的另一個重點就是獲取到精確的時間截。

  查閱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 類型。可以這樣編寫代碼:

surfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);

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

int version = android.os.Build.VERSION.SDK_INT;
if (version >= OSSURPORTFORSURFACETEXTURE) {
    try {
        camera.setPreviewTexture(surfaceTexture);
        int buffersize = WIDTH_COLLECT * HEIGHT_COLLECT
                * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8;
        previewBuffer = new byte[buffersize];
        camera.addCallbackBuffer(previewBuffer);
        camera.setPreviewCallbackWithBuffer(this);
        camera.startPreview();
        isView = true;
    } catch (IOException e) {
        camera.release();
        camera = null;
        e.printStackTrace();
    }
} else {
    ......
}

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

long timestampx;
if (osversion >= OSSURPORTFORSURFACETEXTURE) {
    surfaceTexture.updateTexImage();
    timestampx = surfaceTexture.getTimestamp()/1000000;
    camera.addCallbackBuffer(previewBuffer);
 }

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

通過上面的方法,已經可以在不顯示預覽的情況下獲取到數據幀,並打上極為精確的生成時間截,這對於需要精確計算時間的程序來說是非常有用的。當然是用SurfaceTexture也可以將預覽圖像顯示出來,你可以開一個線程專門來做這件事,而不是在onPreviewFrame中完成。下面提供一段顯示預覽圖像的參考代碼:

try {
    YuvImage image = new YuvImage(data, ImageFormat.NV21,
            WIDTH_COLLECT, HEIGHT_COLLECT, null);
    if (image != null) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        image.compressToJpeg(new Rect(0, 0, WIDTH_COLLECT,
                HEIGHT_COLLECT), 100, stream);
        Bitmap bm = BitmapFactory.decodeByteArray(
                stream.toByteArray(), 0, stream.size());
        stream.close();
        Canvas canvas = previewHolder.lockCanvas();
        canvas.drawBitmap(bm, 0, 0, null);
        previewHolder.unlockCanvasAndPost(canvas);
    }
} catch (Exception e) {
    // TODO: handle exception
}

previewHolder就是要顯示預覽數據的SurfaceView的SurfaceHolder,當然你要可以加上synchronized同步機制。

Demo就沒有上傳了,如果有什麼問題可以直接留言討論。

更多Android相關信息見Android 專題頁面 http://www.linuxidc.com/topicnews.aspx?tid=11

Copyright © Linux教程網 All Rights Reserved