私たちが構築している VR アプリで使用する OpenTok をテストしています。opentok android SDK サンプルを出発点として使用して、GLSurfaceView とその Render インターフェイスの代わりに、CardboardView と対応する StereoRenderer インターフェイスをサポートするように CustomVideoRenderer クラスを変更しました。CardboardView と GLSurfaceView の切り替えはほとんど簡単で、他の場合 (opentok を使用しない他のアプリなど) では正常に機能します。この場合、それはまったく機能せず、シェーダーとテクスチャ設定プロパティをいじくり回した後で、実際に画面に何かを表示することができました! しかし、非常に残念なことに、レンダラー コード (以下に貼り付けます) を試してみると、奇妙な途中の VR ビューが表示されます。立体視ビューは、それぞれの目に必要な歪みでレンダリングされますが、下にあるフレーム イメージはそれぞれの目に複製されません。また、フレームはグレースケールでレンダリングされます! 誰かがこれを適切に機能させた場合、私は本当に助けていただければ幸いです:)
コードは次のとおりです。
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
import android.content.Context;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
import android.view.View;
import com.google.vrtoolkit.cardboard.CardboardView;
import com.google.vrtoolkit.cardboard.EyeTransform;
import com.google.vrtoolkit.cardboard.HeadTransform;
import com.google.vrtoolkit.cardboard.Viewport;
import com.opentok.android.BaseVideoRenderer;
public class CustomVideoStereoRendererWorking extends BaseVideoRenderer {
Context mContext;
CardboardView mView;
MyRenderer mRenderer;
static class MyRenderer implements CardboardView.StereoRenderer {
int mTextureIds[] = new int[3];
float[] mScaleMatrix = new float[16];
private FloatBuffer mVertexBuffer;
private FloatBuffer mTextureBuffer;
private ShortBuffer mDrawListBuffer;
boolean mVideoFitEnabled = true;
boolean mVideoDisabled = false;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
static final int TEXTURECOORDS_PER_VERTEX = 2;
static float mXYZCoords[] = { -1.0f, 1.0f, 0.0f, // top left
-1.0f, -1.0f, 0.0f, // bottom left
1.0f, -1.0f, 0.0f, // bottom right
1.0f, 1.0f, 0.0f // top right
};
static float mUVCoords[] = { 0, 0, // top left
0, 1, // bottom left
1, 1, // bottom right
1, 0 }; // top right
private short mVertexIndex[] = { 0, 1, 2, 0, 2, 3 }; // order to draw
// vertices
private final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
//"attribute vec4 aPosition;\n" +
"attribute vec2 aPosition;\n" +
"attribute vec2 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
//" gl_Position = uMVPMatrix * aPosition;\n" +
" gl_Position = vec4 ( aPosition.x, aPosition.y, 0.0, 1.0 );\n" + //from working renderer
" vTextureCoord = aTextureCoord;\n" +
"}\n";
/*"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 fragmentShaderCode =
"precision mediump float;\n" +
"uniform sampler2D Ytex;\n" +
"uniform sampler2D Utex,Vtex;\n" +
"varying vec2 vTextureCoord;\n" +
"void main(void) {\n" +
" float nx,ny,r,g,b,y,u,v;\n" +
" mediump vec4 txl,ux,vx;" +
" nx=vTextureCoord[0];\n" +
" ny=vTextureCoord[1];\n" +
" y=texture2D(Ytex,vec2(nx,ny)).r;\n" +
" u=texture2D(Utex,vec2(nx,ny)).r;\n" +
" v=texture2D(Vtex,vec2(nx,ny)).r;\n" +
//" y=1.0-1.1643*(y-0.0625);\n" + // Invert effect
//" y=1.1643*(y-0.0625);\n" + // Normal renderer
" u=u-0.5;\n" +
" v=v-0.5;\n" +
" r=y+1.5958*v;\n" +
" g=y-0.39173*u-0.81290*v;\n" +
" b=y+2.017*u;\n" +
" gl_FragColor=vec4(r,g,b,1.0);\n" +
"}\n";
/*
private final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"uniform samplerExternalOES Ytex;\n" +
"uniform samplerExternalOES Utex,Vtex;\n" +
"varying vec2 vTextureCoord;\n" +
"void main(void) {\n" +
" float nx,ny,r,g,b,y,u,v;\n" +
" mediump vec4 txl,ux,vx;" +
" nx=vTextureCoord[0];\n" +
" ny=vTextureCoord[1];\n" +
" y=texture2D(Ytex,vec2(nx,ny)).r;\n" +
" u=texture2D(Utex,vec2(nx,ny)).r;\n" +
" v=texture2D(Vtex,vec2(nx,ny)).r;\n" +
//" y=1.0-1.1643*(y-0.0625);\n" + // Invert effect
//" y=1.1643*(y-0.0625);\n" + // Normal renderer
" u=u-0.5;\n" +
" v=v-0.5;\n" +
" r=y+1.5958*v;\n" +
" g=y-0.39173*u-0.81290*v;\n" +
" b=y+2.017*u;\n" +
" gl_FragColor=vec4(r,g,b,1.0);\n" +
"}\n";
*/
/*"#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" +
"}"*/
ReentrantLock mFrameLock = new ReentrantLock();
Frame mCurrentFrame;
private int mProgram;
private int mTextureWidth;
private int mTextureHeight;
private int mViewportWidth;
private int mViewportHeight;
public MyRenderer() {
ByteBuffer bb = ByteBuffer.allocateDirect(mXYZCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
mVertexBuffer = bb.asFloatBuffer();
mVertexBuffer.put(mXYZCoords);
mVertexBuffer.position(0);
ByteBuffer tb = ByteBuffer.allocateDirect(mUVCoords.length * 4);
tb.order(ByteOrder.nativeOrder());
mTextureBuffer = tb.asFloatBuffer();
mTextureBuffer.put(mUVCoords);
mTextureBuffer.position(0);
ByteBuffer dlb = ByteBuffer.allocateDirect(mVertexIndex.length * 2);
dlb.order(ByteOrder.nativeOrder());
mDrawListBuffer = dlb.asShortBuffer();
mDrawListBuffer.put(mVertexIndex);
mDrawListBuffer.position(0);
}
public void onSurfaceCreated(EGLConfig config) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = 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);
int positionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
int textureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4, mVertexBuffer);
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(textureHandle, TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false, TEXTURECOORDS_PER_VERTEX * 4,
mTextureBuffer);
GLES20.glEnableVertexAttribArray(textureHandle);
GLES20.glUseProgram(mProgram);
int i = GLES20.glGetUniformLocation(mProgram, "Ytex");
GLES20.glUniform1i(i, 0); /* Bind Ytex to texture unit 0 */
i = GLES20.glGetUniformLocation(mProgram, "Utex");
GLES20.glUniform1i(i, 1); /* Bind Utex to texture unit 1 */
i = GLES20.glGetUniformLocation(mProgram, "Vtex");
GLES20.glUniform1i(i, 2); /* Bind Vtex to texture unit 2 */
mTextureWidth = 0;
mTextureHeight = 0;
}
static void initializeTexture(int name, int id, int width, int height) {
GLES20.glActiveTexture(name);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, id);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, width, height, 0, GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE, null);
}
void setupTextures(Frame frame) {
if (mTextureIds[0] != 0) {
GLES20.glDeleteTextures(3, mTextureIds, 0);
}
GLES20.glGenTextures(3, mTextureIds, 0);
int w = frame.getWidth();
int h = frame.getHeight();
int hw = (w + 1) >> 1;
int hh = (h + 1) >> 1;
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h);
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh);
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh);
mTextureWidth = frame.getWidth();
mTextureHeight = frame.getHeight();
}
void updateTextures(Frame frame) {
int width = frame.getWidth();
int height = frame.getHeight();
int half_width = (width + 1) >> 1;
int half_height = (height + 1) >> 1;
int y_size = width * height;
int uv_size = half_width * half_height;
ByteBuffer bb = frame.getBuffer();
// If we are reusing this frame, make sure we reset position and
// limit
bb.clear();
if (bb.remaining() == y_size + uv_size * 2) {
bb.position(0);
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0]);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width, height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, bb);
bb.position(y_size);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1]);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, half_width, half_height, GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE, bb);
bb.position(y_size + uv_size);
GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2]);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, half_width, half_height, GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE, bb);
} else {
mTextureWidth = 0;
mTextureHeight = 0;
}
}
public void onSurfaceChanged(int width, int height) {
GLES20.glViewport(0, 0, width, height);
mViewportWidth = width;
mViewportHeight = height;
}
public void onDrawEye(EyeTransform arg0) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mFrameLock.lock();
if (mCurrentFrame != null && !mVideoDisabled) {
GLES20.glUseProgram(mProgram);
if (mTextureWidth != mCurrentFrame.getWidth() || mTextureHeight != mCurrentFrame.getHeight()) {
setupTextures(mCurrentFrame);
}
updateTextures(mCurrentFrame);
Matrix.setIdentityM(mScaleMatrix, 0);
float scaleX = 1.0f, scaleY = 1.0f;
float ratio = (float) mCurrentFrame.getWidth() / mCurrentFrame.getHeight();
float vratio = (float) mViewportWidth / mViewportHeight;
if (mVideoFitEnabled) {
if (ratio > vratio) {
scaleY = vratio / ratio;
} else {
scaleX = ratio / vratio;
}
} else {
if (ratio < vratio) {
scaleY = vratio / ratio;
} else {
scaleX = ratio / vratio;
}
}
Matrix.scaleM(mScaleMatrix, 0, scaleX * (mCurrentFrame.isMirroredX() ? -1.0f : 1.0f), scaleY, 1);
int mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mScaleMatrix, 0);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.length, GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer);
}
mFrameLock.unlock();
}
public void displayFrame(Frame frame) {
mFrameLock.lock();
if (this.mCurrentFrame != null) {
this.mCurrentFrame.recycle();
}
this.mCurrentFrame = frame;
mFrameLock.unlock();
}
public static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, 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(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
public void disableVideo(boolean b) {
mFrameLock.lock();
mVideoDisabled = b;
if (mVideoDisabled) {
if (this.mCurrentFrame != null) {
this.mCurrentFrame.recycle();
}
this.mCurrentFrame = null;
}
mFrameLock.unlock();
}
public void enableVideoFit(boolean enableVideoFit) {
mVideoFitEnabled = enableVideoFit;
}
public void onFinishFrame(Viewport arg0) {
// TODO Auto-generated method stub
}
public void onNewFrame(HeadTransform arg0) {
// TODO Auto-generated method stub
}
public void onRendererShutdown() {
// TODO Auto-generated method stub
}
};
public CustomVideoStereoRendererWorking(Context context) {
this.mContext = context;
mView = new CardboardView(context);
mView.setEGLContextClientVersion(2);
mView.setVRModeEnabled(true);
mRenderer = new MyRenderer();
mView.setRenderer(mRenderer);
mView.setRenderMode(CardboardView.RENDERMODE_WHEN_DIRTY);
}
@Override
public void onFrame(Frame frame) {
mRenderer.displayFrame(frame);
mView.requestRender();
}
@Override
public void setStyle(String key, String value) {
if (BaseVideoRenderer.STYLE_VIDEO_SCALE.equals(key)) {
if (BaseVideoRenderer.STYLE_VIDEO_FIT.equals(value)) {
mRenderer.enableVideoFit(true);
} else if (BaseVideoRenderer.STYLE_VIDEO_FILL.equals(value)) {
mRenderer.enableVideoFit(false);
}
}
}
@Override
public void onVideoPropertiesChanged(boolean videoEnabled) {
mRenderer.disableVideo(!videoEnabled);
}
@Override
public View getView() {
return mView;
}
@Override
public void onPause() {
mView.onPause();
}
@Override
public void onResume() {
mView.onResume();
}
}