一 概述
前面的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的特点