在上一篇文章中我们介绍了如何使用OpenGL ES预览视频,在文章的末尾提到如果渲染视频的窗口宽高比和视频宽高比不一致会导致视频拉伸,这篇文章将会介绍如何通过视频的缩放来解决这个问题。
我们希望当视频的比例和窗口的比例不一样时,其中一边占满全屏,另一边等比缩放并居中,其余部分显示黑色,这个效果和我们平时使用的视频播放器的效果是一样的,效果如图:
我们在OpenGL ES for Android 预览视频的基础进行如下修改:
修改顶点shader
attribute vec4 a_Position;
attribute vec2 a_TexCoordinate;
varying vec2 v_TexCoord;
uniform mat4 mvpMatrix;
void main()
{
v_TexCoord = a_TexCoordinate;
gl_Position = mvpMatrix*a_Position;
}
在顶点shader中加入mvpMatrix(矩阵)并与a_Position相乘。
获取mvpMatrix索引
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
GLES20.glClearColor(0F, 0F, 0F, 1F)
...
mvpMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "mvpMatrix")
...
}
获取视频及渲染窗口的宽高
override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
screenWidth = width
screenHeight = height
if (videoWidth > 0 && videoHeight > 0) {
computeMatrix()
}
}
mediaPlayer.setOnVideoSizeChangedListener { mp, width, height ->
run {
videoWidth = width
videoHeight = height
if (screenWidth > 0 && screenHeight > 0) {
computeMatrix()
}
}
}
渲染窗口的宽高在onSurfaceChanged中获取,视频的宽高需要给meidaplay添加OnVideoSizeChangedListener回调,由于视频的播放是在onSurfaceCreated中调用且这2个回调都是异步的,因此无法判断先后顺序,因此在这2个回调中都增加判断,只有当视频和渲染窗口的宽高都获取到才计算MVP矩阵,或者我们也可以在onSurfaceChanged中调用视频播放,这样就可以只在OnVideoSizeChangedListener回调中计算矩阵。
计算矩阵
假设视频的宽高比小于屏幕的宽高比,那么视频则在高度上铺满窗口,在宽度上进行缩放,为了不拉伸视频,缩放的系数为1 - ((屏幕的宽高比 - 视频的宽高比) / 2)
,反之如果视频的宽高比大于屏幕的宽高比,视频在高度上的缩放的系数为1 - ((视频的宽高比 - 屏幕的宽高比 ) / 2)
,代码如下:
var modelMatrix = FloatArray(16)
fun computeMatrix() {
val videoRatio = videoWidth / videoHeight.toFloat()
val screenRatio = screenWidth / screenHeight.toFloat()
Matrix.setIdentityM(modelMatrix, 0)
if (videoRatio > screenRatio) {
Matrix.scaleM(modelMatrix, 0, 1F, 1 - ((videoRatio - screenRatio) / 2), 1F)
} else if (videoRatio < screenRatio) {
Matrix.scaleM(modelMatrix, 0, 1 - ((screenRatio - videoRatio) / 2), 1F, 1F)
}
}
绘制
在绘制的时候设置modelMatrix矩阵,代码如下:
override fun onDrawFrame(p0: GL10?) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
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.glUniformMatrix4fv(mvpMatrixLoc, 1, false, modelMatrix, 0)
GLES20.glDrawElements(
GLES20.GL_TRIANGLES,
index.size,
GLES20.GL_UNSIGNED_SHORT,
indexBuffer
)
}
GLES20.glUniformMatrix4fv(mvpMatrixLoc, 1, false, modelMatrix, 0)
为矩阵数据设置,其他已经在OpenGL ES for Android 预览视频中介绍,这里不在介绍。
到这里我们介绍完了视频的缩放功能,想一想如果在欢迎界面播放视频,我们希望视频充满屏幕,而不是出现黑屏,但市场上的手机屏幕比例各种各样,尤其是全面屏、折叠屏的出现,有16:9的、2:1的,还有16:10的,那么如何使用同一个视频去适配所有的屏幕呢?其实一样可以通过视频缩放而解决,只不过一个缩放视频,一个是放大视频。
视频的旋转、平移和缩放是一样的,我们只需要对矩阵进行相应的操作,比如将视频旋转45度,代码如下:
Matrix.rotateM(modelMatrix,0,45F,0F,0F,1F)
效果如下: