OpenGL ES for Android 播放视频_第1张图片

为什么要使用OpenGL ES播放视频

我们都知道Android中有VideoView控件可以直接播放视频,既简单又实用,那么为什么我们还要用OpenGL ES来播放视频呢?那是因为使用OpenGL ES可以做更多的酷炫的动效,比如旋转视频、双指缩放视频、视频的截图、视频的录制、直播、换脸,还有类似“激萌”App里面的特效等这些都是VideoView所无法实现的,而通过OpenGL ES则可以实现这些酷炫的效果,当然这篇文章不会介绍如何这些实现这些效果,如果想了解这些动效请关注我,后面的文章会一一介绍。

开始我们的表演吧,No 图 No Code,先来欣赏下效果图吧:

OpenGL ES for Android 播放视频_第2张图片

shader

首先我们先创建顶点和片段shader,顶点shader代码如下:

attribute vec4 a_Position;attribute vec2 a_TexCoordinate;varying vec2 v_TexCoord;void main(){    v_TexCoord = a_TexCoordinate;    gl_Position = a_Position;}

片段shader代码如下:

#extension GL_OES_EGL_image_external : requireprecision mediump float;uniform samplerExternalOES u_Texture;varying vec2 v_TexCoord;void main(){    gl_FragColor = texture2D(u_Texture, v_TexCoord);}
注意:顶点和片段shader是单独的文件,分别是video_vs.glsl和video_fs.glsl,存放于assets/glsl目录下。

片段shader中u_Texture是纹理,注意它的类型是samplerExternalOES,并不是sampler2D,sampler2D是2D纹理,用于显示图片,而samplerExternalOES是Android特有的类型,用于绘制视频和相机。

program

shader创建好后,我们编译shader并链接到program,然后获取其中参数的句柄,代码如下:

override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {            createProgram()            //获取vPosition索引            vPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_Position")            texCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate")            textureLoc = GLES20.glGetUniformLocation(mProgramHandle, "u_Texture")...        }        private fun createProgram() {            var vertexCode =                AssetsUtils.readAssetsTxt(                    context = context,                    filePath = "glsl/video_vs.glsl"                )            var fragmentCode =                AssetsUtils.readAssetsTxt(                    context = context,                    filePath = "glsl/video_fs.glsl"                )            mProgramHandle = GLTools.createAndLinkProgram(vertexCode, fragmentCode)        }

创建texture

视频纹理的创建和2D纹理的创建略有不同,代码如下:

fun createOESTextureId(): Int {        val textures = IntArray(1)        GLES20.glGenTextures(1, textures, 0)        glCheck("texture generate")        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0])        glCheck("texture bind")        GLES20.glTexParameterf(            GLES11Ext.GL_TEXTURE_EXTERNAL_OES,            GLES20.GL_TEXTURE_MIN_FILTER,            GLES20.GL_LINEAR.toFloat()        )        GLES20.glTexParameterf(            GLES11Ext.GL_TEXTURE_EXTERNAL_OES,            GLES20.GL_TEXTURE_MAG_FILTER,            GLES20.GL_LINEAR.toFloat()        )        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        )        return textures[0]    }

不同之处在于 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]) ,GLES20.glTexParameterf函数的相关参数说明可以参照OpenGL ES 纹理过滤模式-glTexParameteri。
纹理创建成功后返回纹理id,然后创建SurfaceTexture->Surface,将Surface设置给MediaPlayer,代码如下:

override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {...            textureId = GLTools.createOESTextureId()            var surfaceTexture = SurfaceTexture(textureId)            surfaceTexture.setOnFrameAvailableListener(frameAvailableListener)...        }

这里要说下frameAvailableListener,当surfaceTexture有新的一帧数据时将会回调frameAvailableListener,这个时候我们就会更新数据并绘制,在前面的文章我们介绍过在RenderMode=GLSurfaceView.RENDERMODE_WHEN_DIRTY模式下重新绘制需要调用glSurfaceView.requestRender(),因此我们在Activity中实现了frameAvailableListener,并将此实现传递给Renderer,代码如下:

class VideoActivity : AppCompatActivity(), SurfaceTexture.OnFrameAvailableListener {    override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {        glSurfaceView.queueEvent {            surfaceTexture?.updateTexImage()            glSurfaceView.requestRender()        }    }    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.surface)        glSurfaceView.setEGLContextClientVersion(2)        glSurfaceView.setRenderer(MyRenderer(context = baseContext, frameAvailableListener = this))        glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY    }...}

初始化MediaPlayer并播放视频

这里我们使用Android API自带的MediaPlayer,我个人建议如果是商业项目请使用ijkplayer(github开源),不管是自带的MediaPlayer和ijkplayer完成的是视频编解码工作,ijkplayer性能更加稳定、播放的格式更加全面。

MediaPlayer初始化及视频播放代码如下:

override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {            ...            textureId = GLTools.createOESTextureId()            var surfaceTexture = SurfaceTexture(textureId)            surfaceTexture.setOnFrameAvailableListener(frameAvailableListener)            mediaPlayer = MediaPlayer()            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC)            val surface = Surface(surfaceTexture)            mediaPlayer.setSurface(surface)            startVideo()        }fun startVideo() {            try {                mediaPlayer.reset()                val fd = context.assets.openFd("video/lion_chroma.mp4")                mediaPlayer.setDataSource(fd.fileDescriptor,fd.startOffset,fd.length)                mediaPlayer.prepare()                mediaPlayer.start()            } catch (e: Exception) {                Log.e("mqd","$e")            }        }

lion_chroma.mp4视频文件我们存放在assets/video目录下,当然你可以播放SD上或者在线视频。

创建顶点坐标、纹理坐标、顶点索引数据

顶点坐标初始化如下:

        var vertexBuffer = GLTools.array2Buffer(            floatArrayOf(                -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            )        )

纹理坐标初始化如下:

var texBuffer = GLTools.array2Buffer(            floatArrayOf(                0.0f, 0.0f,                0.0f, 1.0f,                1.0f, 1.0f,                1.0f, 0.0f            )        )

顶点索引初始化如下:

var index = shortArrayOf(3, 2, 0, 0, 1, 2)val indexBuffer = GLTools.array2Buffer(index)

绘制

所有准备工作完成后,开始绘制,代码如下:

override fun onDrawFrame(p0: GL10?) {            GLES20.glUseProgram(mProgramHandle)            //设置顶点数据            vertexBuffer.position(0)            GLES20.glEnableVertexAttribArray(vPositionLoc)            GLES20.glVertexAttribPointer(vPositionLoc, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer)            //设置纹理顶点数据            texBuffer.position(0)            GLES20.glEnableVertexAttribArray(texCoordLoc)            GLES20.glVertexAttribPointer(texCoordLoc, 2, GLES20.GL_FLOAT, false, 0, texBuffer)            //设置纹理            GLES20.glActiveTexture(GLES20.GL_TEXTURE0)            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)            GLES20.glUniform1i(textureLoc, 0)            GLES20.glDrawElements(                GLES20.GL_TRIANGLES,                index.size,                GLES20.GL_UNSIGNED_SHORT,                indexBuffer            )        }

到此我们的表演就结束了,运行起来就可以看到开始的效果图了,不过这里有一个小小的瑕疵,如果视频的比例和GLSurfaceView(绘制窗口)的比例不一样的话就会出现视频拉伸的现象,后面的文章我们将会解决这个问题,敬请期待。

更多相关阅读:

  • OpenGL ES for Android 总览

  • OpenGL ES for Android 绘制旋转的地球

  • OpenGL ES for Android 绘制纹理

  • OpenGL ES for Android 绘制立方体

如果这篇文章有帮助到您,希望您关注我的公众号,谢谢。

更多相关文章

  1. Android手机视频客户端项目实训 视频教程打包下载
  2. Android 使用Vitamio打造自己的万能播放器(7)――在线播放(下载视频
  3. Android多媒体功能的实现上(音频,视频,相机,录音)
  4. Android截屏和录制视频
  5. android ”三分屏”视频播放器
  6. 在Android中调用图片、视频、音频、录音、拍照
  7. Android教学视频
  8. android Vitamio Live 实时视频 记录

随机推荐

  1. 可以下载Android 源代码的repo源文件
  2. Android初级教程XUtils实现“断点续传”
  3. Android学习——在线查看android源代码的
  4. android 以httpclient方式把数据提交到服
  5. Android 中常见bug 总结
  6. Android 关于下接框(spinner)的使用
  7. android 上传图片到服务器
  8. android studio 配置checkstyle
  9. (OK) 编译batman-adv.ko—成功—android
  10. android app 与电脑wifi通信