Android 分別使用 SurfaceView 和 TextureView 來預覽 Camera,獲取NV21數據

本文目的

使用 Camera API 進行視頻的採集,分別使用 SurfaceView、TextureView 來預覽 Camera 數據,取到 NV21 的數據回調

SurfaceView優缺點

優點:

可以在一個獨立的線程中進行繪製,不會影響主線程
使用雙緩衝機制,播放視頻時畫面更流暢

缺點:

Surface不在View hierachy中,它的顯示也不受View的屬性控制,所以不能進行平移,縮放等變換,也不能放在其它ViewGroup中。SurfaceView
不能嵌套使用。

TextureView作用

TextureView可用於顯示內容流,內容流可以是視頻或者OpenGL的場景。內容流可來自應用進程或是遠程其它進程。

Textureview必須在硬件加速開啓的窗口中使用。

注意:若是軟解,TextureView不會顯示東西。

TextureView優點及缺點

優點:

支持移動、旋轉、縮放等動畫,支持截圖

缺點:

必須在硬件加速的窗口中使用,佔用內存比SurfaceView高,在5.0以前在主線程渲染,5.0以後有單獨的渲染線程。

準備工作

添加相機權限
<uses-permission android:name="android.permission.CAMERA" />

注意:camera預覽回調中默認使用NV21格式

UI 準備
<!-- 全屏顯示 -->
<style name="FullScreenTheme" parent="AppTheme">
    <item name="windowNoTitle">true</item>
    <item name="android:windowFullscreen">true</item>
</style>

承載預覽圖像

<FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

使用 SurfaceView 預覽 Camera,取到NV21數據

  • 自定義CameraPreview繼承SurfaceView,實現SurfaceHolder.Callback接口
  • 獲取NV21數據,Camera.setPreviewCallback() 要放在Camera.startPreview() 之前。使用Camera.PreviewCallback獲取預覽數據回調。默認是NV21格式。
  • surfaceChanged中,camera啓動預覽前可以進行設置,例如設置尺寸,調整方向
/**
 * camera預覽視圖
 * Created by on 2020/2/26.
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = "CameraPreview";
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private int mFrameCount = 0;

    public CameraPreview(Context context) {
        super(context);
    }

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void setCamera(Camera c) {
        this.mCamera = c;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // 開啓預覽
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "設置相機預覽失敗: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 可在此釋放camera
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // 若需要旋轉、更改大小或重新設置,請確保證已停止預覽
        if (mHolder.getSurface() == null) {
            return;
        }
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            // 在此停止不存在的預覽
        }
        Camera.Parameters parameters = mCamera.getParameters();
        // ImageFormat.NV21 == 17
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.setPreviewCallback(mCameraPreviewCallback); // 回調要放在 startPreview() 之前
            mCamera.startPreview();
        } catch (Exception e) {
            Log.d(TAG, "開啓相機預覽失敗: " + e.getMessage());
        }
    }

    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            mFrameCount++;
            Log.d(TAG, "onPreviewFrame: data.length=" + data.length + ", frameCount=" + mFrameCount);
        }
    };
}

爲了防止阻塞UI線程,在子線程中打開camera。camera常放在try catch中使用。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new InitCameraThread().start();
    }

    @Override
    protected void onResume() {
        if (null == mCamera) {
            if (safeCameraOpen()) {
                mPreview.setCamera(mCamera); // 重新獲取camera操作權
            } else {
                Log.e(TAG, "無法操作camera");
            }
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseCamera();
    }

    private boolean safeCameraOpen() {
        boolean qOpened = false;
        try {
            releaseCamera();
            mCamera = Camera.open();
            qOpened = (mCamera != null);
        } catch (Exception e) {
            Log.e(TAG, "打開相機失敗");
            e.printStackTrace();
        }
        return qOpened;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }

    private class InitCameraThread extends Thread {
        @Override
        public void run() {
            super.run();
            if (safeCameraOpen()) {
                Log.d(TAG, "開啓攝像頭");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mPreview = new CameraPreview(MainActivity.this, mCamera);
                        FrameLayout preview = findViewById(R.id.camera_preview);
                        preview.addView(mPreview);
                    }
                });
            }
        }
    }
}

使用 TextureView 預覽 Camera,取到NV21數據

  • 使用TextureView很簡單:獲取到它的SurfaceTexture,使用SurfaceTexture呈現內容。

  • CameraPreview繼承了TextureView,外部需要傳入camera實例。在onSurfaceTextureAvailable中,配置camera,比如設置圖像方向。

  • 通過設置Camera.PreviewCallback來取得預覽數據。

import java.io.IOException;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import android.view.TextureView;
/**
 * camera預覽視圖
 * Created by on 2020/2/26.
 */
public class CameraPreview extends TextureView implements TextureView.SurfaceTextureListener {
    private static final String TAG = "CameraPreview";
    private Camera mCamera;

    public CameraPreview(Context context) {
        super(context);
    }

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
    }

    public void setCamera(Camera camera) {
        this.mCamera = camera;
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "TextureView onSurfaceTextureAvailable");
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }
        try {
            mCamera.setPreviewCallback(mCameraPreviewCallback);
            mCamera.setPreviewTexture(surface); // 使用SurfaceTexture
            mCamera.startPreview();
        } catch (IOException ioe) {
            // Something bad happened
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "TextureView onSurfaceTextureSizeChanged"); // Ignored, Camera does all the work for us
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        Log.d(TAG, "TextureView onSurfaceTextureDestroyed");
        mCamera.stopPreview();
        mCamera.release();
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        
    }

    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Log.d(TAG, "onPreviewFrame: data.length=" + data.length);
        }
    };
}

操作界面TextureAct。獲取camera操作權,初始化CameraPreview並添加到佈局中。第一次獲取camera時在子線程中操作。

在onPause中釋放camera,onResume中嘗試取回camera控制權。這樣應用暫時退回後臺時,其他應用可以操作攝像頭

public class TextureAct extends AppCompatActivity {
    private static final String TAG = "TextureAct";
    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_texture);
        new InitCameraThread().start();
    }

    @Override
    protected void onResume() {
        if (null == mCamera) {
            if (safeCameraOpen()) {
                mPreview.setCamera(mCamera); // 重新獲取camera操作權
            } else {
                Log.e(TAG, "無法操作camera");
            }
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseCamera();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseCamera();
    }

    private boolean safeCameraOpen() {
        boolean qOpened = false;
        try {
            releaseCamera();
            mCamera = Camera.open();
            qOpened = (mCamera != null);
        } catch (Exception e) {
            Log.e(TAG, "打開相機失敗");
            e.printStackTrace();
        }
        return qOpened;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }

    private class InitCameraThread extends Thread {
        @Override
        public void run() {
            super.run();
            if (safeCameraOpen()) {
                Log.d(TAG, "TextureAct 開啓攝像頭");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mPreview = new CameraPreview(TextureAct.this, mCamera);
                        mPreview.setSurfaceTextureListener(mPreview);
                        FrameLayout preview = findViewById(R.id.camera_preview);
                        preview.addView(mPreview);
                    }
                });
            }
        }
    }
}
  • Textureview必須在硬件加速開啓的窗口中使用。android:hardwareAccelerated=“true” 默認的這個屬性就是true,無需再設置。

  • 每接到一幀數據,就會調用一次onSurfaceTextureUpdated()。通過這個接口。能夠將上來的SurfaceTexture送給OpenGL再去處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章