一、概述
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(); } }