1. 程式人生 > >android平臺下OpenGL ES 3.0使用TextureView對相機Camera預覽實時處理

android平臺下OpenGL ES 3.0使用TextureView對相機Camera預覽實時處理

OpenGL ES 3.0學習實踐

GLSurfaceView和TextureView對比

相信接觸過直播或者視訊播放的朋友,對這兩個控制元件應該很熟悉,這個兩個控制元件都可以用來渲染視訊用的,下面來看下他們的區別:

  • GLSurfaceView

前面的一些部落格都是基於GLSurfaceView來實現渲染的,因為它內部自帶了EGL的管理以及渲染執行緒。另外它定義了使用者需要實現的Render介面,其中EglHelperGLThread分別實現了上面提到的管理EGL環境渲染執行緒的工作。

缺點GLSurfaceView將OpenGL繫結到一起,換言之,GLSurfaceView一但銷燬,伴隨的OpenGL也一起銷燬了,一個OpenGL只能渲染一個GLSurfaceView

  • TextureView

這個控制元件是在在android 4.0中引入的,和SurfaceView不同,它不會在WMS中單獨建立視窗,而是作為一個普通View,因此可以和其它普通View一樣進行移動,旋轉,縮放,動畫等變化。值得注意的是TextureView必須在硬體加速的視窗

中。

缺點使用這個控制元件需要自己來實現EGL管理和渲染執行緒,當然了,雖然麻煩了一點,但是也更為靈活

注意:TextureView本身內建了一個SurfaceTexture,用來配合EGL來將影象顯示到螢幕上,而我們自定義的SurfaceTexture用來接收Camera的預覽影象來做二次處理。

開始實踐

我們現在就可以仿照GLSurfaceView的實現原理,基於HandlerThread來自己實現渲染執行緒和EGL環境管理

回到之後的專案工程,新建TextureEGLHelper

在啟動的CameraTextureActivity中初始化如下程式碼

private void setupView() {
    mTextureView = new TextureView(this);
    setContentView(mTextureView);

	//例項化相機採集類
    mCameraPick = new CameraV1Pick();
    mCameraPick.bindTextureView(mTextureView);
}

因為這裡使用的都是CameraV1版本的相機,所以新建一個CameraV1Pick相機採集類

public class CameraV1Pick implements TextureView.SurfaceTextureListener {

    private static final String TAG = "CameraV1Pick";

    private TextureView mTextureView;

    private int mCameraId;

    private ICamera mCamera;

    private TextureEGLHelper mTextureEglHelper;

    public void bindTextureView(TextureView textureView) {
        this.mTextureView = textureView;
        mTextureEglHelper = new TextureEGLHelper();
        mTextureView.setSurfaceTextureListener(this);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        //載入OES紋理ID
        final int textureId = TextureUtils.loadOESTexture();
        //初始化操作
        mTextureEglHelper.initEgl(mTextureView, textureId);
        //自定義的SurfaceTexture
        SurfaceTexture surfaceTexture = mTextureEglHelper.loadOESTexture();
        //前置攝像頭
        mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        mCamera = new CameraV1((Activity) mTextureView.getContext());
        if (mCamera.openCamera(mCameraId)) {
            mCamera.setPreviewTexture(surfaceTexture);
            mCamera.enablePreview(true);
        } else {
            Log.e(TAG, "openCamera failed");
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        mTextureEglHelper.onSurfaceChanged(width, height);
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        if (mCamera != null) {
            mCamera.enablePreview(false);
            mCamera.closeCamera();
            mCamera = null;
        }
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

    public void onDestroy() {
        if (mTextureEglHelper != null) {
            mTextureEglHelper.onDestroy();
        }
    }
}

上面的註釋比較清楚了,在onSurfaceTextureAvailable的回撥中,首先通過TextureUtils.loadOESTexture方法載入外部紋理textureId,然後通過mTextureEglHelper這個例項初始化EGL環境,接著建立一個基於textureId自定義SurfaceTexture(它對影象流的處理並不直接顯示,而是轉為GL外部紋理,因此可用於影象流資料的二次處理(如Camera濾鏡,桌面特效等),最後開啟相機,將剛才自定義的SurfaceTexture設定到相機中。

其中載入外部紋理ID的loadOESTexture的方法實現如下:

/**
     * 載入OES Texture
     *
     * @return
     */
    public static int loadOESTexture() {
        int[] textureIds = new int[1];
        GLES20.glGenTextures(1, textureIds, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIds[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        return textureIds[0];
    }

封裝EGL環境管理和執行緒渲染類

EGL主要需要四個物件,一個EGLDisplay描述EGL顯示屏,一個EGLConfig,一個EGLContext,一個EGLSurface描述

接下來就是筆者剛剛說到的EGL環境管理和執行緒渲染類,主要用來初始化EGL環境和線上程中渲染。


public class TextureEGLHelper extends HandlerThread implements SurfaceTexture.OnFrameAvailableListener {

    private static final String TAG = "TextureEGLHelper";

    @IntDef({EGLMessage.MSG_INIT, EGLMessage.MSG_RENDER, EGLMessage.MSG_DESTROY})
    @Retention(RetentionPolicy.SOURCE)
    public @interface EGLMessage {
        int MSG_INIT = 100;
        int MSG_RENDER = 200;
        int MSG_DESTROY = 300;
    }

    private HandlerThread mHandlerThread;

    private Handler mHandler;

    private TextureView mTextureView;

    private int mOESTextureId;

    /**
     * 顯示裝置
     */
    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;

    /**
     * EGL上下文
     */
    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;

	/**
	 * 描述幀緩衝區配置引數
	 */
    private EGLConfig[] configs = new EGLConfig[1];

    /**
     * EGL繪圖表面
     */
    private EGLSurface mEglSurface;

	/**
	 * 自定義的SurfaceTexture
	 * 用來接受Camera資料作二次處理
	 */
    private SurfaceTexture mOESSurfaceTexture;

    private CameraTextureRenderer mTextureRenderer;

    private final class TextureHandler extends Handler {

        public TextureHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EGLMessage.MSG_INIT:
                	//筆者使用的是OpenGL ES 3.0
                    initEGLContext(3);
                    return;
                case EGLMessage.MSG_RENDER:
                    //開始渲染
                    drawFrame();
                    return;
                case EGLMessage.MSG_DESTROY:
                    //銷燬
                    return;
                default:
                    return;
            }
        }
    }

    public TextureEGLHelper() {
        super("TextureEGLHelper");
    }

    public void initEgl(TextureView textureView, int textureId) {
        mTextureView = textureView;
        mOESTextureId = textureId;
        //啟動執行緒
        mHandlerThread = new HandlerThread("Renderer Thread");
        mHandlerThread.start();
        mHandler = new TextureHandler(mHandlerThread.getLooper());
        //執行緒中初始化
        mHandler.sendEmptyMessage(EGLMessage.MSG_INIT);
    }

    /**
     * 初始化EGL環境
     *
     * @param clientVersion
     */
    private void initEGLContext(int clientVersion) {
        //獲取預設顯示裝置
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
            throw new RuntimeException("eglGetDisplay error: " + EGL14.eglGetError());
        }
        //存放EGL版本號
        int[] version = new int[2];
        version[0] = 3;
        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
            throw new RuntimeException("eglInitialize error: " + EGL14.eglGetError());
        }
        //配置列表
        int[] attributes = {
                EGL14.EGL_BUFFER_SIZE, 32,
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8,
                EGL14.EGL_ALPHA_SIZE, 8,
                EGL14.EGL_RENDERABLE_TYPE, 4,
                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
                EGL14.EGL_NONE
        };
        int[] numConfigs = new int[1];
        //EGL選擇配置
        if (!EGL14.eglChooseConfig(mEGLDisplay, attributes, 0, configs, 0, configs.length, numConfigs, 0)) {
            throw new RuntimeException("eglChooseConfig error: " + EGL14.eglGetError());
        }
        //獲取TextureView內建的SurfaceTexture作為EGL的繪圖表面,也就是跟系統螢幕打交道
        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        if (surfaceTexture == null) {
            throw new RuntimeException("surfaceTexture is null");
        }
        //建立EGL顯示視窗
        final int[] surfaceAttributes = {EGL14.EGL_NONE};
        mEglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], surfaceTexture, surfaceAttributes, 0);
        //建立上下文環境
        int[] contextAttributes = {
                EGL14.EGL_CONTEXT_CLIENT_VERSION, clientVersion,
                EGL14.EGL_NONE
        };
        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, contextAttributes, 0);

        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY || mEGLContext == EGL14.EGL_NO_CONTEXT) {
            throw new RuntimeException("eglCreateContext fail error: " + EGL14.eglGetError());
        }
        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEGLContext)) {
            throw new RuntimeException("eglMakeCurrent error: " + EGL14.eglGetError());
        }
        //載入渲染器
        mTextureRenderer = new CameraTextureRenderer(mOESTextureId);
        mTextureRenderer.onSurfaceCreated();
    }

    public void onSurfaceChanged(int width, int height) {
        //設定視口
        mTextureRenderer.onSurfaceChanged(width, height);
    }

    private void drawFrame() {
        if (mTextureRenderer != null) {
            //指定mEGLContext為當前系統的EGL上下文
            EGL14.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEGLContext);
            //呼叫渲染器繪製
            mTextureRenderer.onDrawFrame(mOESSurfaceTexture);
            //交換緩衝區,android使用雙緩衝機制,所以我們繪製的都是在後臺緩衝區,通過交換將後臺緩衝區變為前臺顯示區,下一幀的繪製仍然在後臺緩衝區
            EGL14.eglSwapBuffers(mEGLDisplay, mEglSurface);
        }
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        if (mHandler != null) {
            //通知子執行緒渲染
            mHandler.sendEmptyMessage(EGLMessage.MSG_RENDER);
        }
    }

    public SurfaceTexture loadOESTexture() {
    	//載入自定義的SurfaceTexture傳遞給相機
        mOESSurfaceTexture = new SurfaceTexture(mOESTextureId);
        mOESSurfaceTexture.setOnFrameAvailableListener(this);
        return mOESSurfaceTexture;
    }

    /**
     * 銷燬
     * 釋放
     */
    public void onDestroy() {
        if (mHandlerThread != null) {
            mHandlerThread.quitSafely();
        }
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
    }
}

相機操作封裝

ICamera程式碼就不貼了,本身是抽象的一個相機的操作介面類。

public class CameraV1 implements ICamera {

    private Activity mActivity;

    private int mCameraId;

    private Camera mCamera;

    public CameraV1(Activity activity) {
        mActivity = activity;
    }

    /**
     * 開啟相機
     *
     * @param cameraId
     * @return
     */
    public boolean openCamera(int cameraId) {
        try {
            mCameraId = cameraId;
            mCamera = Camera.open(mCameraId);
            setCameraDisplayOrientation(mActivity, mCameraId, mCamera);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 釋放開啟預覽
     *
     * @param enable
     */
    @Override
    public void enablePreview(boolean enable) {
        if (mCamera != null) {
            if (enable) {
                mCamera.startPreview();
            } else {
                mCamera.stopPreview();
            }
        }
    }

    /**
     * 設定相機的旋轉角度
     * 前置相機旋轉270度
     * 後置相機旋轉90度
     *
     * @param activity
     * @param cameraId
     * @param camera
     */
    private void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) {
        Camera.CameraInfo info =
                new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
    }

    public void setPreviewTexture(SurfaceTexture surfaceTexture) {
        if (mCamera != null) {
            try {
                mCamera.setPreviewTexture(surfaceTexture);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 關閉相機釋放資源
     */
    @Override
    public void closeCamera() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
        mActivity = null;
    }

}

實現自定義的渲染器

最後是渲染器程式碼,當然了,筆者這裡是仿照GLSurfaceViewRenderer介面實現的,你也可以根據這個介面自定義實現一個渲染器,主要用來將相機的資料繪製到我們自定義的SurfaceTexture上去,這裡的紋理座標不再細說,可以對照之前的部落格 android平臺下OpenGL ES 3.0使用GLSurfaceView對相機Camera預覽實時處理

/**
 * @anchor: andy
 * @date: 2018-11-11
 * @description: 基於相機
 */
public class CameraTextureRenderer implements ITextureRenderer 
            
           

相關推薦

android臺下OpenGL ES 3.0使用GLSurfaceView相機Camera實時處理

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0使用TextureView相機Camera實時處理

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0從零開始

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0繪製純色背景

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0從矩形中看矩陣和座標系

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0繪製彩色三角形

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0繪製圓點、直線和三角形

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0著色語言基礎知識(下)

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0例項詳解頂點屬性、頂點陣列

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0例項詳解頂點緩衝區物件(VBO)和頂點陣列物件(VAO)

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0繪製立方體的幾種方式

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0實現2D紋理貼圖顯示bitmap

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E

android臺下OpenGL ES 3.0從矩形中看矩陣和正交投影

OpenGL ES 3.0學習實踐 目錄 繪製矩形 新建一個矩形渲染器: public class RectangleRenderer implements GLSurfaceView.Renderer 定義頂點著色器: #version 300 es l

Android臺下OpenGL圖形編程

alloc arch jsb config ble _array itl conf graphics http://blog.csdn.net/jason0539/article/details/9164885 https://developer.android.com/

Android 臺下OpenGL繪製立方體(2)

圖形類 ——本文用所引法繪製 構造方中初始化資料 和渲染器 private void initData() { //獲得 頂點 顏色 和 索引的緩衝資料 //頂點 verBuffer = getFloa

Android臺下OpenGL初步

轉自網上,網上沒找到出處,只看到一些論壇中有這篇文章,組織的有點混亂,這篇文章感覺講的挺好的。本文只關注於如何一步步實現在Android平臺下運用OpenGl。 1、GLSurfaceViewGLSurfaceView是Android應用程式中實現OpenGl畫圖的重要組成部

Android 臺下OpenGL繪製立方體(1)

寫在文前的話 回顧Opengl繪製圖形的開發步驟 1.新建自己的View 實現 GLSurfaceView 2.初始化著色器Render 1)設定Opengl 版本 非必需 2)設定著色器 3)設定渲染模式 4)實現 onSurfaceCr

Android 為例編寫一個 OpenGL ES 3.0 例項,Native & Java 兩種實現

一、簡介 通過這個 Sample,你將瞭解到 Android 中是怎麼使用 OpenGL ES 通過繪製一個簡單的靜態三角形,來簡單入門和了解它大致的流程(類似於 HelloWorld 工程) 介紹使用 Native 層 和 Java 層 兩種方式來分別實現

一個簡單的OpenGL ES 3.0 示例 (Android NDK jni)

OpenGL ES 3.0 上的一個三角形例子,網上可以下載到android skd 版(java)和 android ndk (c&c++版) 為了瞭解一下JNI,於是寫了如下小程式。 這個例子是使用jni, java中呼叫c中的程式碼完成三角形的渲染, 其中sh

android studio | openGL es 3.0增強現實(AR)開發 (1) 建立一個openGL es 3.0開發環境

1.什麼是NDK,什麼是JNI? NDK:Native Development Kit(原生開發工具包), NDK允許使用者使用類似C / C++之類的原生程式碼語言執行部分程式。它包括下面的部分(1)從C / C++生成原生程式碼庫所需要的工具和buil