[OpenGL]从零开始写一个Android平台下的全景视频播放器——2.1 使用GLSurfaceView和MediaPlayer播放一个平面视频(上)

回到目录

MediaPlayer的生命周期

了解MediaPlayer的生命周期的好处是,当我们遇到各种状态异常时,可以很方便的找到出错的原因 ,具体的可以看这篇参考资料

了解了MediaPlayer的生命周期后,我们大概可以把播放器的状态设定成这样(仅供参考):

public enum PanoStatus
{
    IDLE, PREPARED,BUFFERING, PLAYING, PAUSED_BY_USER, PAUSED, STOPPED, COMPLETE, ERROR
} enum PanoStatus
{
    IDLE, PREPARED,BUFFERING, PLAYING, PAUSED_BY_USER, PAUSED, STOPPED, COMPLETE, ERROR
}

之所以要区分PAUSEDPAUSED_BY_USER,是因为当我们的播放器被直接切换到后台时应该暂停,而切换回来时,如果已经被用户暂停,不应该继续播放,否则应该继续播放(当然,这只是个人观点)。

MediaPlayer初始化

MediaPlayer初始化代码可以这样写:

mediaPlayer=new MediaPlayer();
try{
    mediaPlayer.setDataSource(context, Uri.parse(videoPath));
}catch (IOException e){
    e.printStackTrace();
}
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setLooping(true);new MediaPlayer();
try{
    mediaPlayer.setDataSource(context, Uri.parse(videoPath));
}catch (IOException e){
    e.printStackTrace();
}
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setLooping(true);

videoPath是我们通过其他类传递过来的视频地址,为了方便,上面一段代码我直接写到了构造函数中,现在构造函数的形式如下:

public GLRenderer(Context context,String videoPath) GLRenderer(Context context,String videoPath)

使用SurfaceTexture来设置MediaPlayer的输出

和图片不同的是,视频需要不断地刷新,每当有新的一帧来时,我们都应该更新纹理,然后重新绘制。

创建一个纹理

什么,创建的纹理难道不应该和之前一样的吗?因为视频的每一帧都可以看成图片啊。 
主要的原因是,MediaPlayer的输出往往不是RGB格式(一般是YUV),而GLSurfaceView需要RGB格式才能正常显示,另外,获取每一帧的数据并没有那么方便。 
所以,我们创建的纹理应该稍有不同,在这之前,我们先来看看官方对于SurfaceTexture的定义:

<em>/** * Captures frames from an image stream as an OpenGL ES texture. * * <p>The image stream may come from either camera preview <span style="color:#954121">or</span> video decode. A * {<span style="color:#008080">@link</span> android.view.Surface} created from a SurfaceTexture can be used as an output * destination <span style="color:#954121">for</span> the {<span style="color:#008080">@link</span> android.hardware.camera2}, {<span style="color:#008080">@link</span> android.media.MediaCodec}, * {<span style="color:#008080">@link</span> android.media.MediaPlayer}, <span style="color:#954121">and</span> {<span style="color:#008080">@link</span> android.renderscript.Allocation} APIs. * When {<span style="color:#008080">@link</span> #updateTexImage} is called, the contents of the texture object specified * when the SurfaceTexture was created are updated to contain the most recent image from the image * stream. This may cause some frames of the stream to be skipped. */</em>

SurfaceTexture的主要作用就是,从视频流和相机数据流获取新一帧的数据,获取新数据调用的方法是updateTexImage

{@link android.view.Surface} created from a SurfaceTexture can be used as an output

这句话的意思是,我们要用SurfaceTexture 创建一个Surface,然后将这个Surface作为MediaPlayer的输出表面。 
用SurfaceView做过视频播放的话,应该比较好理解,区别在于,这个输出可以不显示出来,取决于你的render逻辑,这样在处理视频的时候就更自由一些。

创建SurfaceTexture

那SurfaceTexture 要怎么创建呢,我们先在onSurfaceCreated中将生成纹理的代码改成这样:

int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);

textureId = textures[0];
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
ShaderUtils.checkGlError("glBindTexture mTextureID");

GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
        GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
        GLES20.GL_LINEAR);[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);

textureId = textures[0];
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
ShaderUtils.checkGlError("glBindTexture mTextureID");

GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
        GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
        GLES20.GL_LINEAR);

和之前比较明显的区别是,我们用GLES11Ext.GL_TEXTURE_EXTERNAL_OES来代替了GLES20.GL_TEXTURE_2D,而且我们也没给这个纹理传递bitmap数据(废话,视频都还没放呢哪来的数据)

GLES11Ext.GL_TEXTURE_EXTERNAL_OES的用处是什么? 
之前提到视频解码的输出格式是YUV的(YUV420sp,应该是),那么这个扩展纹理的作用就是实现YUV格式到RGB的自动转化,我们就不需要再为此写YUV转RGB的代码了(如果你搜索相机Preview相关资料,会发现大量的YUV转RGB的实现)

我们给GLRenderer加入如下成员变量

private SurfaceTexture surfaceTexture;
private MediaPlayer mediaPlayer;
private boolean updateSurface; SurfaceTexture surfaceTexture;
private MediaPlayer mediaPlayer;
private boolean updateSurface;

然后在onSurfaceCreated的最后加上如下代码:

surfaceTexture = new SurfaceTexture(textureId);
surfaceTexture.setOnFrameAvailableListener(this);

Surface surface = new Surface(surfaceTexture);
mediaPlayer.setSurface(surface);
surface.release();new SurfaceTexture(textureId);
surfaceTexture.setOnFrameAvailableListener(this);

Surface surface = new Surface(surfaceTexture);
mediaPlayer.setSurface(surface);
surface.release();

我们用之前生成的textureId去创建一个SurfaceTexture,然后用surfaceTexture去创建一个Surface ,再把surface设定为mediaPlayer的输出表面。 
setOnFrameAvailableListener的作用,就是监听是否有新的一帧数据到来,我们设置为this,是让GLRenderer 来实现这个接口,如下所示:

public class GLRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener class GLRenderer implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener
@Override
synchronized public void onFrameAvailable(SurfaceTexture surface) {
    updateSurface = true;
}
synchronized public void onFrameAvailable(SurfaceTexture surface) {
    updateSurface = true;
}

在有新数据时,用updateTexImage来更新纹理,这个getTransformMatrix的目的,是让新的纹理和纹理坐标系能够正确的对应,mSTMatrix的定义是和projectionMatrix完全一样的。

private float[] mSTMatrix = new float[16]; float[] mSTMatrix = new float[16];

是不是感觉多了很多乱七八糟的东西? 
没关系,下一节会有更多乱七八糟的东西。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值