Android 最简单的视频播放器之OpenGL ES视频渲染工具封装(三)
一、概述
OpenGL ES的全称是OpenGL for Embeded System,它是OpenGL的一个子集。其本质上一个一个图形图像处理库。OpenGL ES是优化版,专门针对嵌入式设备的,性能非常的优异。
下面的代码就是利用OpenGL ES 的java api来做视频的渲染工作(主要是操作纹理)
案例:封装一个用于渲染视频的Drawer和Render 为视频最终渲染到GLSurfaceView上做准备、封装一个AudioPlayer为音频可以顺利渲染的扬声器上做准备
二、代码实例
1.VideoDrawer.java:OpenGL渲染器
import android.graphics.SurfaceTexture; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.Matrix; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; /** * @ProjectName: TheSimpllestplayer * @Package: com.yw.thesimpllestplayer.renderview * @ClassName: VideoDrawer * @Description: 视频渲染器 * @Author: wei.yang * @CreateDate: 2021/11/6 14:23 * @UpdateUser: 更新者:wei.yang * @UpdateDate: 2021/11/6 14:23 * @UpdateRemark: 更新说明: * @Version: 1.0 */ public class VideoDrawer implements IDrawer { public VideoDrawer() { //1.初始化顶点坐标 initPos(); } /** * 顶点坐标,此处的坐标系是物体坐标系:中心店坐标是(0,0) */ private float[] mVertexCoors = new float[]{ -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f }; /** * 纹理坐标系,中心坐标点为(0.5,0.5),上方向为t从0~1,右边方向为s,从0~1.刚好和计算器物理坐标系是反过来的。 */ private float[] mTextureCoors = new float[]{ 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f }; private String vertextShaderSource = "attribute vec4 aPosition;" + "precision mediump float;" + "uniform mat4 uMatrix;" + "attribute vec2 aCoordinate;" + "varying vec2 vCoordinate;" + "attribute float alpha;" + "varying float inAlpha;" + "void main(){" + "gl_Position = uMatrix*aPosition;" + "vCoordinate = aCoordinate;" + "inAlpha = alpha;" + "}"; private String fragmentShaderSource = "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;" + "varying vec2 vCoordinate;" + "varying float inAlpha;" + "uniform samplerExternalOES uTexture;" + "void main() {" + "vec4 color = texture2D(uTexture, vCoordinate);" + "gl_FragColor = vec4(color.r, color.g, color.b, inAlpha);" + "}"; /** * 视频宽高 */ private int mVideoWidth = -1; private int mVideoHeight = -1; /** * 物理屏幕的宽高 */ private int mWorldWidth = -1; private int mWorldHeight = -1; /** * 纹理ID */ private int mTextureId = -1; /** * 定义SurfaceTexture 为显示视频做准备 */ private SurfaceTexture mSurfaceTexture = null; /** * 定义OpenGL 程序ID */ private int mProgram = -1; /** * 矩阵变换接受者(shader中) */ private int mVertexMatrixHandler = -1; /** * 顶点坐标接收者 */ private int mVertexPosHandler = -1; /** * 纹理坐标接受者 */ private int mTexturePosHandler = -1; /** * 纹理接受者 */ private int mTextureHandler = -1; /** * 半透明值接受者 */ private int mAlphaHandler = -1; /** * 顶点缓冲 */ private FloatBuffer mVertexBuffer = null; /** * 纹理缓冲 */ private FloatBuffer mTextureBuffer = null; /** * 矩阵 */ private float[] mMatrix = null; /** * 透明度 */ private float mAlpha = 1f; /** * 旋转角度 */ private float mWidthRatio = 1f; private float mHeightRatio = 1f; private int floatLength = 16; /** * 初始化顶点坐标 */ private void initPos() { ByteBuffer vByteBuffer = ByteBuffer.allocateDirect(mVertexCoors.length * 4); vByteBuffer.order(ByteOrder.nativeOrder()); //将坐标转换为floatbuffer,用以传给opengl程序 mVertexBuffer = vByteBuffer.asFloatBuffer(); mVertexBuffer.put(mVertexCoors); mVertexBuffer.position(0); ByteBuffer tByteBuffer = ByteBuffer.allocateDirect(mTextureCoors.length * 4); tByteBuffer.order(ByteOrder.nativeOrder()); mTextureBuffer = tByteBuffer.asFloatBuffer(); mTextureBuffer.put(mTextureCoors); mTextureBuffer.position(0); } /** * 初始化矩阵变换,主要是防止视频拉伸变形 */ private void initDefMatrix() { if (mMatrix != null) return; if (mVideoWidth != -1 && mVideoHeight != -1 && mWorldWidth != -1 && mWorldHeight != -1) { mMatrix = new float[floatLength]; float[] prjMatrix = new float[floatLength]; float originRatio = mVideoWidth / (float) mVideoHeight; float worldRatio = mWorldWidth / (float) mWorldHeight; if (mWorldWidth > mWorldHeight) { if (originRatio > worldRatio) { mHeightRatio = originRatio / worldRatio; Matrix.orthoM( prjMatrix, 0, -mWidthRatio, mWidthRatio, -mHeightRatio, mHeightRatio, 3f, 5f ); } else {// 原始比例小于窗口比例,缩放高度度会导致高度超出,因此,高度以窗口为准,缩放宽度 mWidthRatio = worldRatio / originRatio; Matrix.orthoM( prjMatrix, 0, -mWidthRatio, mWidthRatio, -mHeightRatio, mHeightRatio, 3f, 5f ); } } else { if (originRatio > worldRatio) { mHeightRatio = originRatio / worldRatio; Matrix.orthoM( prjMatrix, 0, -mWidthRatio, mWidthRatio, -mHeightRatio, mHeightRatio, 3f, 5f ); } else {// 原始比例小于窗口比例,缩放高度会导致高度超出,因此,高度以窗口为准,缩放宽度 mWidthRatio = worldRatio / originRatio; Matrix.orthoM( prjMatrix, 0, -mWidthRatio, mWidthRatio, -mHeightRatio, mHeightRatio, 3f, 5f ); } } //设置相机位置 float[] viewMatrix = new float[floatLength]; Matrix.setLookAtM( viewMatrix, 0, 0f, 0f, 5.0f, 0f, 0f, 0f, 0f, 1.0f, 0f ); //计算变换矩阵 Matrix.multiplyMM(mMatrix, 0, prjMatrix, 0, viewMatrix, 0); } } @Override public void setVideoSize(int videoWidth, int videoHeight) { mVideoWidth = videoWidth; mVideoHeight = videoHeight; } @Override public void setWorldSize(int worldWidth, int worldHeight) { mWorldWidth = worldWidth; mWorldHeight = worldHeight; } @Override public void setAlpha(float alpha) { mAlpha = alpha; } @Override public void draw() { if (mTextureId != -1) { initDefMatrix(); //2/创建、编译、启动opengles着色器 createGLPrg(); //3.激活并绑定纹理单元 activateTexture(); //4.绑定图元到纹理单元 updateTexture(); //5.开始绘制渲染 doDraw(); } } @Override public void setTextureID(int textureID) { mTextureId = textureID; //根据textureId初始化一个SurfaceTexture mSurfaceTexture = new SurfaceTexture(textureID); } public SurfaceTexture getSurfaceTexture() { return mSurfaceTexture; } /** * 创建并使用opengles程序 */ private void createGLPrg() { if (mProgram == -1) { int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertextShaderSource); int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderSource); //创建programe陈谷 mProgram = GLES20.glCreateProgram(); //将顶点着色器加入程序 GLES20.glAttachShader(mProgram, vertexShader); //将片元着色器加入程序 GLES20.glAttachShader(mProgram, fragmentShader); GLES20.glLinkProgram(mProgram); //从程序中获取句柄 mVertexMatrixHandler = GLES20.glGetUniformLocation(mProgram, "uMatrix"); mVertexPosHandler = GLES20.glGetAttribLocation(mProgram, "aPosition"); mTextureHandler = GLES20.glGetUniformLocation(mProgram, "uTexture"); mTexturePosHandler = GLES20.glGetAttribLocation(mProgram, "aCoordinate"); mAlphaHandler = GLES20.glGetAttribLocation(mProgram, "alpha"); } //使用opengl程序 GLES20.glUseProgram(mProgram); } /** * 激活并绑定纹理单元 */ private void activateTexture() { //激活指定纹理单元 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //绑定纹理ID到纹理单元 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId); //将激活并绑定的纹理id传递到着色器里面 GLES20.glUniform1i(mTextureHandler, 0); //配置边缘过滤参数 GLES20.glTexParameterf( GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR ); GLES20.glTexParameterf( GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR ); //配置s轴和t轴的方式 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 ); } private void updateTexture() { mSurfaceTexture.updateTexImage(); } /** * 加载着色器 * * @param shaderType 着色器类型 * @param shaderCode 着色器代码 * @return */ private int loadShader(int shaderType, String shaderCode) { //根据着色器类型创建着色器 int shader = GLES20.glCreateShader(shaderType); //将着色其代码加入到着色器 GLES20.glShaderSource(shader, shaderCode); //编译zhuoseq GLES20.glCompileShader(shader); return shader; } /** * 开始绘制渲染 */ public void doDraw() { //启用顶点坐标句柄 GLES20.glEnableVertexAttribArray(mVertexPosHandler); GLES20.glEnableVertexAttribArray(mTexturePosHandler); GLES20.glUniformMatrix4fv(mVertexMatrixHandler, 1, false, mMatrix, 0); //设置着色器参数, 第二个参数表示一个顶点包含的数据数量,这里为xy,所以为2 GLES20.glVertexAttribPointer(mVertexPosHandler, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer); GLES20.glVertexAttribPointer( mTexturePosHandler, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer ); GLES20.glVertexAttrib1f(mAlphaHandler, mAlpha); //开始绘制 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } @Override public void release() { GLES20.glDisableVertexAttribArray(mVertexPosHandler); GLES20.glDisableVertexAttribArray(mTexturePosHandler); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); GLES20.glDeleteTextures(1, new int[]{mTextureId}, 0); GLES20.glDeleteProgram(mProgram); } public void translate(float dx, float dy) { Matrix.translateM(mMatrix, 0, dx * mWidthRatio * 2, -dy * mHeightRatio * 2, 0f); } public void scale(float sx, float sy) { Matrix.scaleM(mMatrix, 0, sx, sy, 1f); mWidthRatio /= sx; mHeightRatio /= sy; } }
2.VideoRender.java:视频渲染器,其继承了Render接口
import android.opengl.GLES20; import android.opengl.GLSurfaceView; import java.util.ArrayList; import java.util.List; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** * @ProjectName: TheSimpllestplayer * @Package: com.yw.thesimpllestplayer.renderview * @ClassName: VideoRender * @Description: OpenGL渲染器 * @Author: wei.yang * @CreateDate: 2021/11/6 15:38 * @UpdateUser: 更新者:wei.yang * @UpdateDate: 2021/11/6 15:38 * @UpdateRemark: 更新说明: * @Version: 1.0 */ public class VideoRender implements GLSurfaceView.Renderer { private final List<IDrawer> drawers = new ArrayList<IDrawer>(); @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { GLES20.glClearColor(0f, 0f, 0f, 0f); //开启混合,即半透明 GLES20.glEnable(GLES20.GL_BLEND); GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); int[] textureIds = OpenGLTool.getInstance().createTextureIds(drawers.size()); for (int i = 0; i < textureIds.length; i++) { drawers.get(i).setTextureID(textureIds[i]); } } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES20.glViewport(0, 0, width, height); for (IDrawer drawer : drawers) { drawer.setWorldSize(width, height); } } @Override public void onDrawFrame(GL10 gl) { //清除颜色缓冲和深度缓冲 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); for (int i = 0; i < drawers.size(); i++) { drawers.get(i).draw(); } } /** * 添加渲染器 * * @param drawer */ public void addDrawer(IDrawer drawer) { drawers.add(drawer); } }
3.AudioPlayer.java:使用AudioTrack封装的音频播放器
import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.media.MediaCodec; import android.media.MediaFormat; import android.util.Log; import java.nio.ByteBuffer; /** * @ProjectName: TheSimpllestplayer * @Package: com.yw.thesimpllestplayer.audioplayer * @ClassName: AudioPlayer * @Description: 音频播放器 * @Author: wei.yang * @CreateDate: 2021/11/6 13:57 * @UpdateUser: 更新者:wei.yang * @UpdateDate: 2021/11/6 13:57 * @UpdateRemark: 更新说明: * @Version: 1.0 */ public class AudioPlayer { private static final String TAG = "AudioPlayer"; /** * 采样率 */ private int mSampleRate = -1; /** * 声音通道数量 */ private int mChannels = 1; /** * PCM采样位数 */ private int mPCMEncodeBit = AudioFormat.ENCODING_PCM_16BIT; /** * 音频播放器 */ private AudioTrack mAudioTrack = null; /** * 音频数据缓存 */ private short[] mAudioOutTempBuf = null; public AudioPlayer(MediaFormat format) { try { mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); if (format.containsKey(MediaFormat.KEY_PCM_ENCODING)) { mPCMEncodeBit = format.getInteger(MediaFormat.KEY_PCM_ENCODING); } else { //如果没有这个参数,默认为16位采样 mPCMEncodeBit = AudioFormat.ENCODING_PCM_16BIT; } } catch (Exception e) { Log.e(TAG, e.getMessage()); } } /** * 初始化audioplayer */ public void initPlayer(){ int channelType = AudioFormat.CHANNEL_OUT_MONO; if (mChannels == 1) { channelType = AudioFormat.CHANNEL_OUT_MONO;//单声道 } else { channelType = AudioFormat.CHANNEL_OUT_STEREO;//双声道 } //获取最小缓冲区 int minBufferSize = AudioTrack.getMinBufferSize(mSampleRate, channelType, mPCMEncodeBit); mAudioOutTempBuf = new short[minBufferSize / 2]; mAudioTrack = new AudioTrack( AudioManager.STREAM_MUSIC,//播放类型,音乐 mSampleRate,//采用率 channelType,//通道数 mPCMEncodeBit,//采样位数 minBufferSize,//缓冲区大小 AudioTrack.MODE_STREAM//播放模式,数据流动态写入,static是一次性写入 ); mAudioTrack.play(); } /** * 播放音频 * * @param outputBuffer 音频数据缓冲区 * @param bufferInfo 缓冲区信息 */ public void play(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo) { if (mAudioOutTempBuf.length < bufferInfo.size / 2) { mAudioOutTempBuf = new short[bufferInfo.size / 2]; } outputBuffer.position(0); outputBuffer.asShortBuffer().get(mAudioOutTempBuf, 0, bufferInfo.size / 2); //开始播放 mAudioTrack.write(mAudioOutTempBuf, 0, bufferInfo.size / 2); } /** * 暂停 */ public void pause() { mAudioTrack.pause(); } /** * 停止 */ public void stop() { mAudioTrack.stop(); } /** * 销毁 */ public void release() { mAudioTrack.release(); } }