一 概述

前面的OpenGLES一系列基础都是为了VR视频播放来铺垫的,没有OpenGLES的基础理解VR视频播放和具体绘制会有很大困难。只要看过前面的一系列OpenGLES的基础加上涉及使用OpenGLES播放视频文章,那么下面的VR视频播放就显得很简单。其中比较关键的是OpenGL ES之十二——地球仪和VR图和Android中可以用来“播放”视频的View。

重点功能实现:

1. VR视频播放;
2. 播放模式分为:全景视频和VR眼镜模式;重力感应模式和普通模式;

VR视频播放的几个关键点

VR视频的播放的大概原理:我们假设自己处在一个巨型球里面且处在球心的位置,而视频图像就呈现在球的内表面上。 有了上面的原理以及OpenGLES 的基础,下面是实现的几个关键点:

1. 在三维空间中为一个球建模(也就是将球的坐标以数组的形式存储起来,这里涉及顶点坐标以及纹理坐标);
2. 将视频解码出来,我们将解码好的视频图像呈现到球的内表面上(利用Surface,SurfaceTexture将IjkPlayer中获取视频图像展示在球内表面);
3. 我们监听手机传感器的数据变化,以达到图像随手机位置角度变化而变化(设置转动矩阵实时调整);
4. VR眼镜模式(视口的个数调整);

二 关键代码

下面是关键代码,最终的完整代码会在最后给出。

球建模

//计算顶点位置和纹理位置数据
    private void calculateAttribute(){
        float radius=2f;
        final double angleSpan = Math.PI/90f;// 将球进行单位切分的角度

        ArrayList<Float> alVertix = new ArrayList<>();
        ArrayList<Float> textureVertix = new ArrayList<>();
        for (double vAngle = 0; vAngle < Math.PI; vAngle = vAngle + angleSpan){

            for (double hAngle = 0; hAngle < 2*Math.PI; hAngle = hAngle + angleSpan){
                float x0 = (float) (radius* Math.sin(vAngle) * Math.cos(hAngle));
                float y0 = (float) (radius* Math.sin(vAngle) * Math.sin(hAngle));
                float z0 = (float) (radius * Math.cos((vAngle)));

                float x1 = (float) (radius* Math.sin(vAngle) * Math.cos(hAngle + angleSpan));
                float y1 = (float) (radius* Math.sin(vAngle) * Math.sin(hAngle + angleSpan));
                float z1 = (float) (radius * Math.cos(vAngle));

                float x2 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.cos(hAngle + angleSpan));
                float y2 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.sin(hAngle + angleSpan));
                float z2 = (float) (radius * Math.cos(vAngle + angleSpan));

                float x3 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.cos(hAngle));
                float y3 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.sin(hAngle));
                float z3 = (float) (radius * Math.cos(vAngle + angleSpan));

                alVertix.add(x1);
                alVertix.add(y1);
                alVertix.add(z1);
                alVertix.add(x0);
                alVertix.add(y0);
                alVertix.add(z0);
                alVertix.add(x3);
                alVertix.add(y3);
                alVertix.add(z3);

                float s0 = (float) (hAngle / Math.PI/2);
                float s1 = (float) ((hAngle + angleSpan)/Math.PI/2);
                float t0 = (float) (vAngle / Math.PI);
                float t1 = (float) ((vAngle + angleSpan) / Math.PI);

                textureVertix.add(s1);// x1 y1对应纹理坐标
                textureVertix.add(t0);
                textureVertix.add(s0);// x0 y0对应纹理坐标
                textureVertix.add(t0);
                textureVertix.add(s0);// x3 y3对应纹理坐标
                textureVertix.add(t1);

                alVertix.add(x1);
                alVertix.add(y1);
                alVertix.add(z1);
                alVertix.add(x3);
                alVertix.add(y3);
                alVertix.add(z3);
                alVertix.add(x2);
                alVertix.add(y2);
                alVertix.add(z2);

                textureVertix.add(s1);// x1 y1对应纹理坐标
                textureVertix.add(t0);
                textureVertix.add(s0);// x3 y3对应纹理坐标
                textureVertix.add(t1);
                textureVertix.add(s1);// x2 y3对应纹理坐标
                textureVertix.add(t1);
            }
        }

        vCount = alVertix.size() / 3;
        vertexBuffer = convertToFloatBuffer(alVertix);
        mTexVertexBuffer = convertToFloatBuffer(textureVertix);
    }

    //动态数组转FloatBuffer
    private FloatBuffer convertToFloatBuffer(ArrayList<Float> data){
        float[] d=new float[data.size()];
        for (int i=0;i<d.length;i++){
            d[i]=data.get(i);
        }

        ByteBuffer buffer=ByteBuffer.allocateDirect(data.size()*4);
        buffer.order(ByteOrder.nativeOrder());
        FloatBuffer ret=buffer.asFloatBuffer();
        ret.put(d);
        ret.position(0);
        return ret;
    }

视频图像呈现到球的内表面上

最终绘制调用OpenGLES的代码完成绘制操作和之前没有什么区别,在OpenGLES中都是绘制三角形,这里不赘述。关键在于从视频的解码器IjkPlayer中获取图像和纹理关联起来,如下:

//初始化纹理id
    private void initTextureId() {
        int[] textures = new int[1];

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

        textureId = textures[0];
    }

	//播放器和纹理绑定
    private void attachTexture() {
        Mlog.e("==attachTexture");
        Mlog.e("textureId=="+textureId);
        surfaceTexture = new SurfaceTexture(textureId);
        surfaceTexture.setOnFrameAvailableListener(this);//监听是否有新的一帧数据到来
        Surface surface = new Surface(surfaceTexture);
        ijkMediaPlayer.setSurface(surface);
        surface.release();
    }

监听手机传感器的数据变化

Activity中
//传感器的数据监听
    private void initSensor() {
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mRotation=sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
        sensorManager.registerListener(new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent event) {
                setRotateMatrix(event);
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {

            }
        }, mRotation, SensorManager.SENSOR_DELAY_GAME);
    }

    private void setRotateMatrix(SensorEvent event) {
        //Log.e("setRotateMatrix","setRotateMatrix");
        //横竖屏转换处理
        SensorManager.getRotationMatrixFromVector(mTempRotateMatrix, event.values);
        float[] values = event.values;
        switch (mDeviceRotation){
            case Surface.ROTATION_0:
                SensorManager.getRotationMatrixFromVector(mRotateMatrix, values);
                break;
            case Surface.ROTATION_90:
                SensorManager.getRotationMatrixFromVector(mTempRotateMatrix, values);
                SensorManager.remapCoordinateSystem(mTempRotateMatrix, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, mRotateMatrix);
                break;
        }

		//将旋转矩阵传入渲染器
        glVideoRenderer.setuRotateMatrix(mRotateMatrix);
    }
渲染器中
public void setuRotateMatrix(float[] rotateMatrix) {
        mTempRotateMatrix = rotateMatrix;
        if (interactionModeNormal){
            System.arraycopy(mTempRotateMatrix, 0, mRotateMatrix, 0, 16);
        }
    }
最后是比较关键的着色器文件
顶点着色器
attribute vec4 aPosition;//顶点位置
attribute vec2 aTexCoord;//S T 纹理坐标

uniform mat4 uProjMatrix;
uniform mat4 uRotateMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;

varying vec2 vTexCoord;
void main() {
    vTexCoord = aTexCoord;
    gl_Position = uProjMatrix*uRotateMatrix*uViewMatrix*uModelMatrix*aPosition;
}
片段着色器
#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 vTexCoord;
uniform samplerExternalOES sTexture;
void main() {
    gl_FragColor=texture2D(sTexture, vTexCoord);
}

这是VR视频播放的源码链接,这是一个demo,帮助大家理解VR视频播放原理。同时里面还有SurfaceView,TextureVIew播放普通视频的简单示例,帮助大家对比一下各个可以播放视频view的特点