使用 OpenGL 2.0 API 在 GLSurfaceView 上将 Android 相机预览旋转 90 度

Posted

技术标签:

【中文标题】使用 OpenGL 2.0 API 在 GLSurfaceView 上将 Android 相机预览旋转 90 度【英文标题】:Rotating Android Camera Preview by 90 degrees on GLSurfaceView using OpenGL 2.0 APIs 【发布时间】:2016-08-10 18:45:26 【问题描述】:

注意我是 android 和 OpenGL 的新手。

我使用代码在屏幕上显示 Android 相机预览。我想在下面的“MainRenderer”类中使用 OpenGL 2.0 将相机角度旋转 90 度(由于我的应用程序处于纵向模式,因此以纵向分辨率匹配我的相机)。我查看了Android camera rotate,但它没有执行轮换。

在布局 xml 中设置“android:rotation='90'”会旋转表面视图,但会产生黑色表面 - 没有相机预览。

由于我使用的是 GLSurfaceView,我现在希望通过执行矩阵旋转和纵横比调整来实际旋转 OpenGL 2.0 中的相机预览。像这样的帖子很少 How to rotate a camera view in OpenGL 2.0 ES 和 http://www.programcreek.com/java-api-examples/index.php?class=android.opengl.Matrix&method=rotateM 但由于我不熟悉 OpenGL(我希望尽快熟悉),我无法将 API 与下面的代码联系起来。

我知道也许 rotateM 和 translateM(猜测使用 ModelViewProjection Matrix)可能会做我打算完成的事情。

我想知道下面可能需要更改 vtmp 和 ttmp 吗?

感谢任何帮助。

原代码来自http://maninara.blogspot.com/2012/09/render-camera-preview-using-opengl-es.html

//在清单中

< uses-feature android:glEsVersion="0x00020000" android:required="true"/>
< uses-feature android:name="android.hardware.camera"/>
< uses-permission android:name="android.permission.CAMERA"/>
< uses-permission android:name="android.permission.WAKE_LOCK"/>
... android:screenOrientation="landscape" ... // activity property

// 活动

public class MainActivity extends Activity 
  private MainView mView;
  private WakeLock mWL;

  @Override
  public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    // full screen & full brightness
    requestWindowFeature ( Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    mWL = ((PowerManager)getSystemService ( Context.POWER_SERVICE )).newWakeLock(PowerManager.FULL_WAKE_LOCK, "WakeLock");
    mWL.acquire();
    mView = new MainView(this);
    setContentView ( mView );
  

  @Override
  protected void onPause() 
    if ( mWL.isHeld() )
      mWL.release();
    mView.onPause();
    super.onPause();
  

  @Override
  protected void onResume() 
    super.onResume();
    mView.onResume();
    if(!mWL.isHeld()) mWL.acquire();
  

// 查看

class MainView extends GLSurfaceView 
  MainRenderer mRenderer;

  MainView ( Context context ) 
    super ( context );
    mRenderer = new MainRenderer(this);
    setEGLContextClientVersion ( 2 );
    setRenderer ( mRenderer );
    setRenderMode ( GLSurfaceView.RENDERMODE_WHEN_DIRTY );
  

  public void surfaceCreated ( SurfaceHolder holder ) 
    super.surfaceCreated ( holder );
  

  public void surfaceDestroyed ( SurfaceHolder holder ) 
    mRenderer.close();
    super.surfaceDestroyed ( holder );
  

  public void surfaceChanged ( SurfaceHolder holder, int format, int w, int h ) 
    super.surfaceChanged ( holder, format, w, h );
  

// 渲染器

public class MainRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener 
  private final String vss =
      "attribute vec2 vPosition;\n" +
      "attribute vec2 vTexCoord;\n" +
      "varying vec2 texCoord;\n" +
      "void main() \n" +
      "  texCoord = vTexCoord;\n" +
      "  gl_Position = vec4 ( vPosition.x, vPosition.y, 0.0, 1.0 );\n" +
      "";

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

  private int[] hTex;
  private FloatBuffer pVertex;
  private FloatBuffer pTexCoord;
  private int hProgram;

  private Camera mCamera;
  private SurfaceTexture mSTexture;

  private boolean mUpdateST = false;

  private MainView mView;

  MainRenderer ( MainView view ) 
    mView = view;
    float[] vtmp =  1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f ;
    float[] ttmp =  1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f ;
    pVertex = ByteBuffer.allocateDirect(8*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    pVertex.put ( vtmp );
    pVertex.position(0);
    pTexCoord = ByteBuffer.allocateDirect(8*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    pTexCoord.put ( ttmp );
    pTexCoord.position(0);
  

  public void close()
  
    mUpdateST = false;
    mSTexture.release();
    mCamera.stopPreview();
    mCamera.release();
    mCamera = null;
    deleteTex();
  

  public void onSurfaceCreated ( GL10 unused, EGLConfig config ) 
    //String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS);
    //Log.i("mr", "Gl extensions: " + extensions);
    //Assert.assertTrue(extensions.contains("OES_EGL_image_external"));

    initTex();
    mSTexture = new SurfaceTexture ( hTex[0] );
    mSTexture.setOnFrameAvailableListener(this);

    mCamera = Camera.open();
    try 
      mCamera.setPreviewTexture(mSTexture);
     catch ( IOException ioe ) 
    

    GLES20.glClearColor ( 1.0f, 1.0f, 0.0f, 1.0f );

    hProgram = loadShader ( vss, fss );
  

  public void onDrawFrame ( GL10 unused ) 
    GLES20.glClear( GLES20.GL_COLOR_BUFFER_BIT );

    synchronized(this) 
      if ( mUpdateST ) 
    mSTexture.updateTexImage();
    mUpdateST = false;
      
    

    GLES20.glUseProgram(hProgram);

    int ph = GLES20.glGetAttribLocation(hProgram, "vPosition");
    int tch = GLES20.glGetAttribLocation ( hProgram, "vTexCoord" );
    int th = GLES20.glGetUniformLocation ( hProgram, "sTexture" );

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, hTex[0]);
    GLES20.glUniform1i(th, 0);

    GLES20.glVertexAttribPointer(ph, 2, GLES20.GL_FLOAT, false, 4*2, pVertex);
    GLES20.glVertexAttribPointer(tch, 2, GLES20.GL_FLOAT, false, 4*2, pTexCoord );
    GLES20.glEnableVertexAttribArray(ph);
    GLES20.glEnableVertexAttribArray(tch);

    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    GLES20.glFlush();
  

  public void onSurfaceChanged ( GL10 unused, int width, int height ) 
    GLES20.glViewport( 0, 0, width, height );
    Camera.Parameters param = mCamera.getParameters();
    List psize = param.getSupportedPreviewSizes();
    if ( psize.size() > 0 ) 
      int i;
      for ( i = 0; i < psize.size(); i++ ) 
    if ( psize.get(i).width < width || psize.get(i).height < height )
      break;
      
      if ( i > 0 )
    i--;
      param.setPreviewSize(psize.get(i).width, psize.get(i).height);
      //Log.i("mr","ssize: "+psize.get(i).width+", "+psize.get(i).height);
    
    param.set("orientation", "landscape");
    mCamera.setParameters ( param );
    mCamera.startPreview();
  

  private void initTex() 
    hTex = new int[1];
    GLES20.glGenTextures ( 1, hTex, 0 );
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, hTex[0]);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
  

  private void deleteTex() 
    GLES20.glDeleteTextures ( 1, hTex, 0 );
  

  public synchronized void onFrameAvailable ( SurfaceTexture st ) 
    mUpdateST = true;
    mView.requestRender();
  

  private static int loadShader ( String vss, String fss ) 
    int vshader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
    GLES20.glShaderSource(vshader, vss);
    GLES20.glCompileShader(vshader);
    int[] compiled = new int[1];
    GLES20.glGetShaderiv(vshader, GLES20.GL_COMPILE_STATUS, compiled, 0);
    if (compiled[0] == 0) 
      Log.e("Shader", "Could not compile vshader");
      Log.v("Shader", "Could not compile vshader:"+GLES20.glGetShaderInfoLog(vshader));
      GLES20.glDeleteShader(vshader);
      vshader = 0;
    

    int fshader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
    GLES20.glShaderSource(fshader, fss);
    GLES20.glCompileShader(fshader);
    GLES20.glGetShaderiv(fshader, GLES20.GL_COMPILE_STATUS, compiled, 0);
    if (compiled[0] == 0) 
      Log.e("Shader", "Could not compile fshader");
      Log.v("Shader", "Could not compile fshader:"+GLES20.glGetShaderInfoLog(fshader));
      GLES20.glDeleteShader(fshader);
      fshader = 0;
    

    int program = GLES20.glCreateProgram();
    GLES20.glAttachShader(program, vshader);
    GLES20.glAttachShader(program, fshader);
    GLES20.glLinkProgram(program);

    return program;
  

【问题讨论】:

如果你真的想走 OpenGL ES 路线,请参阅 Grafika (github.com/google/grafika) 中的“来自相机的纹理”。它演示了相机预览的任意位置、旋转、缩放和“缩放”,并输出到 SurfaceView。另外:您似乎正在请求唤醒锁定权限;请务必阅读***.com/questions/2131948/force-screen-on/…。 @fadden 谢谢。是的,唤醒锁文章似乎有所帮助。 【参考方案1】:

我认为有几种方法取决于什么更适合你:

    根据角度更改顶点数组值(可能是最简单的一种) Camera.setDisplayOrientation 您可以稍微更改您的顶点(它与您目前所拥有的有点不同,我使用 vec4 而不是 vec2,需要进行一些其他更改以在您的代码中考虑到这一点):
static final String vss =
    "uniform mat4 uMVPMatrix;\n" +
    "uniform mat4 uSTMatrix;\n" +
    "attribute vec4 vPosition;\n" +
    "attribute vec4 vTexCoord;\n" +
    "varying vec2 texCoord;\n" +
    "void main() \n" +
    "    gl_Position = uMVPMatrix * vPosition;\n" +
    "    texCoord = (uSTMatrix * vTexCoord).xy;\n" +
    "\n";

然后

float[] mvpMatrix = new float[16];
int uMVPMatrixHandle;

uMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
checkLocation(uMVPMatrixHandle, "uMVPMatrix");

在绘图时指定您想要的任何角度:

Matrix.setIdentityM(mvpMatrix, 0);
Matrix.rotateM(mvpMatrix, 0, angle, 0f, 0f, 1f);
GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, mvpMatrix, 0);
EGL.checkGlError("glUniformMatrix4fv");

【讨论】:

谢谢。我从***.com/questions/35227222/… 和上面的其他部分添加了 checkGLError 和 checkLocation 函数。但是,当我根据您的建议运行代码时,它似乎崩溃了。为了清楚起见,您列出的代码的哪些部分应该放在“onDraw”中,哪些应该放在“onSurfaceCreated”、“onSurfaceChanged”中? Camera.setDisplayOrientation 对我不起作用。我使用了第一个选项,我拿了纸和铅笔,在坐标系上画了一个简单的图表。然后我按逻辑放置所有坐标,并根据它更改vtmp 如下,float[] vtmp = -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f ; 旋转 90 度。它适用于我的应用程序。 很好,是的,当需要更多控制和额外步骤时,最后一种方法很好。我认为第一个最适合您的情况,但请确保考虑到 90 度角是一个动态/变量,您运行的每个设备可能会有所不同(有关角度处理的更多信息,请参阅developer.android.com/reference/android/hardware/…),而不是那样,您应该没事。 你好,我已经检查了ist答案,它工作正常,但是有一个pblm,如何克服镜像效应?

以上是关于使用 OpenGL 2.0 API 在 GLSurfaceView 上将 Android 相机预览旋转 90 度的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅OpenGL ES 2.0和3.x系列的API Reference Guide 参考卡片地址

OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解III(纹理)

OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解III(纹理)

在 Android 上的 OpenGL ES 2.0 中使用 VBO/IBO

OpenGL学习随笔-- OpenGL ES 2.0渲染管线

关于arcgis for android 2.0 中MapView退出的时候报错 call to OpenGL ES API with No Current context