使用 SurfaceTexture 和 OpenGL 修改相机输出

Posted

技术标签:

【中文标题】使用 SurfaceTexture 和 OpenGL 修改相机输出【英文标题】:Modifying camera output using SurfaceTexture and OpenGL 【发布时间】:2012-09-13 05:01:46 【问题描述】:

我正在尝试通过 openGL 过滤器运行来自相机硬件的流,然后在 GLSurfaceView 中显示它来过滤来自相机硬件的流。当 openGL 去渲染帧时,LogCat 反复吐出一个错误:

[unnamed-3314-0] updateTexImage:清除 GL 错误:0x502

0x502 是一个通用的 openGL 错误,并不能真正帮助我找出问题所在。这是代码如何工作的序列(或者至少应该像我想象的那样工作),我已经在下面复制了我的代码。我希望其他人能看到我的问题。

    创建新的 MyGLSurfaceView。这也在内部创建了新的 MyGL20Renderer 对象。这个 MyGLSurfaceView 被设置为内容视图。 一旦 MyGLSurfaceView 完成膨胀/初始化,此完成事件将触发渲染器创建 DirectVideo 绘制对象,该对象编译/链接定义的着色器并将它们添加到 openGL 程序。然后它会创建一个新的 openGL 纹理对象,然后使用纹理对象 ID 回调 MainActivity。 当从渲染器调用 MainActivity 方法时,它会使用传递的 openGL 纹理对象创建一个新的 SurfaceTexture 对象。然后它将自己设置为表面的 onFrameListener。然后它创建/打开相机对象,将创建的 SurfaceTexture 设置为视频流的目标,并启动相机源。 当提要中的帧可用时,onFrameAvailable 会向渲染器发送渲染请求。这是在 openGL 线程上获取的,该线程调用 SurfaceTexture 的 updateTexImage(),它将帧内存加载到 openGL 纹理中。然后它调用 DirectVideo 的绘图对象,并运行 openGL 程序序列。 如果我注释掉这个 .draw() 行,上面提到的错误就会消失,所以问题可能出在这里的某个地方,但我不排除它是由不正确的链接/创建的纹理引起的。

MainActivity.java

public class MainActivity extends Activity implements SurfaceTexture.OnFrameAvailableListener

    private Camera mCamera;
    private MyGLSurfaceView glSurfaceView;
    private SurfaceTexture surface;
    MyGL20Renderer renderer;

    @Override
    public void onCreate(Bundle savedInstanceState)
    
        super.onCreate(savedInstanceState);

        glSurfaceView = new MyGLSurfaceView(this);
        renderer = glSurfaceView.getRenderer();
        setContentView(glSurfaceView);
    

    public void startCamera(int texture)
    
        surface = new SurfaceTexture(texture);
        surface.setOnFrameAvailableListener(this);
        renderer.setSurface(surface);

        mCamera = Camera.open();

        try
        
            mCamera.setPreviewTexture(surface);
            mCamera.startPreview();
        
        catch (IOException ioe)
        
            Log.w("MainActivity","CAM LAUNCH FAILED");
        
    

    public void onFrameAvailable(SurfaceTexture surfaceTexture)
    
        glSurfaceView.requestRender();
    

    @Override
    public void onPause()
    
        mCamera.stopPreview();
        mCamera.release();
        System.exit(0);
    

MyGLSurfaceView.java

class MyGLSurfaceView extends GLSurfaceView

    MyGL20Renderer renderer;
    public MyGLSurfaceView(Context context)
    
        super(context);

        setEGLContextClientVersion(2);

        renderer = new MyGL20Renderer((MainActivity)context);
        setRenderer(renderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

    
    public MyGL20Renderer getRenderer()
    
        return renderer;
    

MyGL20Renderer.java

public class MyGL20Renderer implements GLSurfaceView.Renderer


    DirectVideo mDirectVideo;
    int texture;
    private SurfaceTexture surface;
    MainActivity delegate;

    public MyGL20Renderer(MainActivity _delegate)
    
        delegate = _delegate;
    

    public void onSurfaceCreated(GL10 unused, EGLConfig config)
    
        mDirectVideo = new DirectVideo(texture);
        texture = createTexture();
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        delegate.startCamera(texture);
    

    public void onDrawFrame(GL10 unused)
    
            float[] mtx = new float[16];
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
            surface.updateTexImage();
            surface.getTransformMatrix(mtx); 

            mDirectVideo.draw();
    

    public void onSurfaceChanged(GL10 unused, int width, int height)
    
        GLES20.glViewport(0, 0, width, height);
    

    static public int loadShader(int type, String shaderCode)
    
        int shader = GLES20.glCreateShader(type);

        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    

    static private int createTexture()
    
        int[] texture = new int[1];

        GLES20.glGenTextures(1,texture, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);        
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
     GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
     GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
             GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

        return texture[0];
    

    public void setSurface(SurfaceTexture _surface)
    
        surface = _surface;
    

DirectVideo.java

public class DirectVideo 


    private final String vertexShaderCode =
            "#extension GL_OES_EGL_image_external : require\n"+
            "attribute vec4 position;" +
            "attribute vec4 inputTextureCoordinate;" +
            "varying vec2 textureCoordinate;" +
            "void main()" +
            ""+
                "gl_Position = position;"+
                "textureCoordinate = inputTextureCoordinate.xy;" +
            "";

        private final String fragmentShaderCode =
            "#extension GL_OES_EGL_image_external : require\n"+
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() " +
            "  gl_FragColor = vColor;" +
            "";

        private FloatBuffer vertexBuffer, textureVerticesBuffer;
        private ShortBuffer drawListBuffer;
         private final int mProgram;
            private int mPositionHandle;
            private int mColorHandle;
            private int mTextureCoordHandle;


    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 2;
    static float squareVertices[] =  // in counterclockwise order:
         -1.0f,  1.0f,
         -1.0f,  -1.0f,
         1.0f,  -1.0f,
         1.0f,  1.0f
    ;

    private short drawOrder[] =  0, 1, 2, 0, 2, 3 ; // order to draw vertices

    static float textureVertices[] =  // in counterclockwise order:
        1.0f,  1.0f,
        1.0f,  0.0f,
        0.0f,  1.0f,
        0.0f,  0.0f
   ;

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    private int texture;

    public DirectVideo(int _texture)
    
        texture = _texture;

        ByteBuffer bb = ByteBuffer.allocateDirect(squareVertices.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareVertices);
        vertexBuffer.position(0);

        ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);

        ByteBuffer bb2 = ByteBuffer.allocateDirect(textureVertices.length * 4);
        bb2.order(ByteOrder.nativeOrder());
        textureVerticesBuffer = bb2.asFloatBuffer();
        textureVerticesBuffer.put(textureVertices);
        textureVerticesBuffer.position(0);

        int vertexShader = MyGL20Renderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShader = MyGL20Renderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

        mProgram = GLES20.glCreateProgram();             // create empty OpenGL ES Program
        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
        GLES20.glLinkProgram(mProgram);          
    

    public void draw()
    
        GLES20.glUseProgram(mProgram);

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);

        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "position");
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,vertexStride, vertexBuffer);

        mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
        GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
        GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,vertexStride, textureVerticesBuffer);

        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
        GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
        GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
    

【问题讨论】:

502 是GL_INVALID_OPERATION。您可以在调用 updateTexImage 之前尝试显式检查错误吗? Assert.assertTrue(GLES20.glGetError() == 0); 可能是您所做的某些事情导致了错误,并且在 updateTexImage 调用它之前不会对其进行查询。从该错误消息中不清楚该错误是由 updateTexImage 引起的,还是之前的未决错误。 确实,你是对的。 draw 函数在初始化期间运行良好,但在第一个 onFrameAvailable 发出渲染请求时进行的第一次 draw() 调用,ASSERT 失败。 您必须在整个绘图函数中添加断言,直到找到导致错误的 OpenGL 调用。 实际上更进一步;假设 glGetError 将“错误”重置为 0,问题可能出在绘图函数中。当在 updateTexImage() 调用之前、在 updateTexImage() 调用之后但在 DirectVideo.draw() 调用之前以及在 DirectVideo.draw() 调用之后调用 onDrawFrame 时,我将调试输出设置为没有错误。前两个通过没有错误,但第三个没有。所以,我之前发布的行为似乎是前一帧渲染触发 Assert 的残留错误。 好的,我会尝试一下,看看会出现什么。谢谢! 【参考方案1】:
mDirectVideo = new DirectVideo(texture);
texture = createTexture();

应该是

texture = createTexture();
mDirectVideo = new DirectVideo(texture);

着色器

private final String vertexShaderCode =
        "attribute vec4 position;" +
        "attribute vec2 inputTextureCoordinate;" +
        "varying vec2 textureCoordinate;" +
        "void main()" +
        ""+
            "gl_Position = position;"+
            "textureCoordinate = inputTextureCoordinate;" +
        "";

    private final String fragmentShaderCode =
        "#extension GL_OES_EGL_image_external : require\n"+
        "precision mediump float;" +
        "varying vec2 textureCoordinate;                            \n" +
        "uniform samplerExternalOES s_texture;               \n" +
        "void main() " +
        "  gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
        "";

mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

应该是

mColorHandle = GLES20.glGetAttribLocation(mProgram, "s_texture");

从 DirectVideo draw.glVertexAttribPointer 等中删除初始化的东西。把它放在一些初始化函数中。

public void draw()

    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
            GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

【讨论】:

我相信这确实解决了它。谢谢! (并被接受;迟到总比没有好)。 hmm.. @DanCollins 我遵循了所有这些步骤,但只有一个灰屏......有什么建议吗? 坐标也关闭了,奇怪的三角形。对于本地电话使用,请使用:static float squareVertices[] = -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f ; static float textureVertices[] = 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, ; 我已经根据您的建议尝试了该项目,并创建了一个 GitHub 存储库,结果为:link。我移动了 GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,vertexStride, textureVerticesBuffer);方法调用 DirectVideo 以使其正常工作。显示的图像仍然是镜像反转的。非常欢迎任何解决此问题的建议。 要移除镜像反转,请将 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 替换为 0.0f, 1.0f, 1.0 f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,

以上是关于使用 SurfaceTexture 和 OpenGL 修改相机输出的主要内容,如果未能解决你的问题,请参考以下文章

玩转Android Camera开发:使用TextureView和SurfaceTexture预览Camera 基础拍照demo

Android Camera开发:使用TextureView和SurfaceTexture预览Camera 基础拍照demo

玩转Android Camera开发:使用TextureView和SurfaceTexture预览Camera 基础拍照demo

如何以类似于相机为 SurfaceTexture 生成帧的方式生成 YUVI420 帧到 SurfaceTexture?

与 MediaPlayer 一起使用后如何清除 SurfaceTexture?

优化流媒体vbo openg