MediaCodec解码H264字节流文件,用OpenGL ES 2.0渲染

1.数据流
byte[] decodeData;
2.解码
a.初始化解码器

public void initAndStart(int width, int height){
        try {
            mediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
        }catch (IOException e) {
            Log.e(TAG, "Error:" + e.toString());
        }
        //初始化解码器格式 预设宽高
        mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
        //设置帧率
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
            //fix 4.1 Bug
            mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
        }
        mediaCodec.configure(mediaFormat, null, null, 0);
        mediaCodec.start();
        inputBuffers = mediaCodec.getInputBuffers();
        outputBuffers = mediaCodec.getOutputBuffers();
        bufferInfo = new MediaCodec.BufferInfo();
    }

b.数据input

 private void input(byte[] frameData) {
        ByteBuffer inputBuffer = null;
        //API>=21区分
        // 1.-1表示一直等待 2.0表示不等待 3.大于0表示等待的时间(us)
        inIndex = mediaCodec.dequeueInputBuffer(-1);
        if (inIndex >= 0) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                inputBuffer = mediaCodec.getInputBuffer(inIndex);
            } else {
                inputBuffer = inputBuffers[inIndex];
            }
            inputBuffer.clear();
            inputBuffer.put(frameData, 0, frameData.length);
            mediaCodec.queueInputBuffer(inIndex,0, frameData.length, mTimeSample,0);
            inIndex = mediaCodec.dequeueInputBuffer(0);
        } else if(inIndex == -1);
    }
c.数据output
private byte[] output(){
        ByteBuffer outputBuffer = null;
        while(true) {
            outIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 1000);
            if(outIndex >= 0){
                getBuffer = new byte[bufferInfo.size];
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    outputBuffer = mediaCodec.getOutputBuffer(outIndex);
                }else {
                    outputBuffer = outputBuffers[outIndex];
                }

                //获取解码output缓冲区的image数据
                MediaFormat outformat = mediaCodec.getOutputFormat();
                outVideoWidth = outformat.getInteger(MediaFormat.KEY_WIDTH);
                outVideoHeight = outformat.getInteger(MediaFormat.KEY_HEIGHT);
                if (keyStride > 0 && keyStrideHeight > 0 && outVideoWidth != keyStride || outVideoHeight != keyStrideHeight) {
                    outVideoWidth = keyStride;
                    outVideoHeight = keyStrideHeight;
                }
                outputBuffer.get(getBuffer);
                outputBuffer.position(bufferInfo.offset);
                outputBuffer.rewind();
                mediaCodec.releaseOutputBuffer(outIndex, false);
            }else if(outIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
            }else{
                break;
            }
        }
        return getBuffer;
    }

从output缓冲区拿到的解码视频,width和height不一定和输入流的宽高相等,所以在output方法中获取一下这两个值,方便渲染
d.释放资源

public void release() {
        mediaCodec.stop();
        mediaCodec.release();
        mediaCodec = null;
        inputBuffers = null;
        outputBuffers = null;
    }

PS:在解码推流的时候,需要注意,如果使用的while或者其他循环推送,那么init动作要在循环体外面初始化一次就行,每次推流都初始化,则无法从缓冲区拿到想要的解码数据。
3.渲染解码数据
片段着色器脚本 fragment shader
gl_FragColor:
Fragment Shader的输出,它是一个四维变量(或称为 vec4)。
表示在经过着色器代码处理后,正在呈现的像素的 R、G、B、A 值。
顶点着色器脚本 vertex shader
gl_Position:原始的顶点数据在Vertex Shader中经过平移、旋转、缩放等数学变换后,
生成新的顶点位置(一个四维 (vec4) 变量,包含顶点的 x、y、z 和 w 值)。
新的顶点位置通过在Vertex Shader中写入gl_Position传递到渲染管线的后继阶段继续处理。

1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败
GLES20.glCreateShader(shaderType);
2.如果着色器创建成功, 为创建的着色器加载脚本代码
GLES20.glShaderSource(shader, source);
3.编译已经加载脚本代码的着色器
GLES20.glCompileShader(shader);
4.获取着色器的编译情况, 如果结果为0, 说明编译失败
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);

package com.tutk.IOTC.openGL;

import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class OpenGLforMediaCodec {

    private static final String TAG = "OpenGLforMediaCodec";
    private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
    private EGLContext eglCtx = EGL14.EGL_NO_CONTEXT;
    private EGLDisplay eglDis = EGL14.EGL_NO_DISPLAY;
    private EGLConfig eglConfig = null;



    /*片段着色器脚本 fragment shader
     *gl_FragColor:
     * Fragment Shader的输出,它是一个四维变量(或称为 vec4)。
     * 表示在经过着色器代码处理后,正在呈现的像素的 R、G、B、A 值。
     * */
    private String fragmentShader =
            "precision mediump float;\n" +
                    "varying vec2 coordinate;\n" +
                    "uniform sampler2D tex_y;\n" +
                    "uniform sampler2D tex_u;\n" +
                    "uniform sampler2D tex_v;\n" +
                    "void main()\n" +
                    "{\n" +
                    "    vec3 yuv;\n" +
                    "    vec3 mrgb;    \n" +
                    "    yuv.x = texture2D(tex_y, coordinate).r;        \n" +//得到yuv数据坐标矩阵
                    "    yuv.y = texture2D(tex_u, coordinate).r - 0.5; \n" +
                    "    yuv.z = texture2D(tex_v, coordinate).r - 0.5; \n" +
                    "    mrgb = mat3( 1,       1,         1,\n" +
                    "                0,       -0.39173,  2.017,\n" +
                    "                1.5958,  -0.81290,  0) * yuv;  " +
                    "    gl_FragColor = vec4(mrgb.rgb, 1.0);\n" +
                    "}\n";
    /*顶点着色器脚本 vertex shader
     * gl_Position:原始的顶点数据在Vertex Shader中经过平移、旋转、缩放等数学变换后,
     * 生成新的顶点位置(一个四维 (vec4) 变量,包含顶点的 x、y、z 和 w 值)。
     * 新的顶点位置通过在Vertex Shader中写入gl_Position传递到渲染管线的后继阶段继续处理。
     * */
    private String vertexShader =
            "attribute vec4 position;\n" +
                    "attribute  vec2 textureCoordinate;\n" +//要获取的纹理坐标
                    "varying  vec2 coordinate;\n" + //传递给fragm shader的纹理坐标,会自动插值
                    "void main() { \n" +
                    "    gl_Position = position; \n" +
                    "    coordinate = textureCoordinate;\n" +
                    "}\n";

    private SurfaceHolder surfaceHolder;

    private  int programId = 3;
    public OpenGLforMediaCodec(){
    }

    //初始化EGL程序
    public void initShader(){
        programId = ShaderUtils.createProgram(vertexShader, fragmentShader);
        vertexBuffer = floatBufferUtil(vertexData);
        textureVertexBuffer = floatBufferUtil(textureVertexData);
    }
    public void initEGL(SurfaceHolder mSurfaceHolder) {
        surfaceHolder = mSurfaceHolder;
        if(eglDis == EGL14.EGL_NO_DISPLAY) {
            eglDis = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
            if (eglDis == EGL14.EGL_NO_DISPLAY){
                Log.e(TAG,"eglGetDisplay error :" + EGL14.eglGetError());
            }
        }
        boolean success;
        int[] majorVersion = new int[1];
        int[] minorVersion = new int[1];
        success = EGL14.eglInitialize(eglDis, majorVersion, 0, minorVersion, 0);
        if(!success){
            Log.e(TAG, "Unable to initialize EGL");
        }
        int confAttr[] = {
//                EGL14.EGL_BUFFER_SIZE,
                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8, //EGL_RED_SIZE,EGL_GREEN_SIZE,EGL_BLUE_SIZE 表示我们最终渲染的图形是RGB格式
//                EGL14.EGL_ALPHA_SIZE, 8,
                EGL14.EGL_NONE   //常量结束符
        };//获取framebuffer格式和能力
        EGLConfig[] configs = new EGLConfig[1];
        int [] numConfigs = new int[1];//存放获取的config数据
        success = EGL14.eglChooseConfig(eglDis, confAttr, 0, configs, 0, configs.length, numConfigs, 0);
        if (!success){
            Log.e(TAG,"some config is wrong :" + EGL14.eglGetError());
        }
        eglConfig = configs[0];
        //创建OpenGL上下文
        int ctxAttr[] = {
                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,// openGL 2.0
                EGL14.EGL_NONE
        };
        eglCtx = EGL14.eglCreateContext(eglDis, configs[0], EGL14.EGL_NO_CONTEXT, ctxAttr, 0);
        if(eglCtx == EGL14.EGL_NO_CONTEXT){
            Log.e(TAG,"context failed:" + EGL14.eglGetError());
        }
        int[] surfaceAttr = {
                EGL14.EGL_NONE
        };
        //OpenGL显示层和本地窗口ANativeWindow的绑定
        if(surfaceHolder.getSurface().isValid()) {//fix bug,create window surface will fail when surface is invalid
            eglSurface = EGL14.eglCreateWindowSurface(eglDis, configs[0], surfaceHolder, surfaceAttr, 0);
        }
        if(eglSurface ==  EGL14.EGL_NO_SURFACE) {
            switch (EGL14.eglGetError()) {
                case EGL14.EGL_BAD_ALLOC:
                    // Not enough resources available. Handle and recover
                    Log.e(TAG, "Not enough resources available");
                    break;
                case EGL14.EGL_BAD_CONFIG:
                    // Verify that provided EGLConfig is valid
                    Log.e(TAG, "provided EGLConfig is invalid");
                    break;
                case EGL14.EGL_BAD_PARAMETER:
                    // Verify that the EGL_WIDTH and EGL_HEIGHT are
                    // non-negative values
                    Log.e(TAG, "provided EGL_WIDTH and EGL_HEIGHT is invalid");
                    break;
                case EGL14.EGL_BAD_MATCH:
                    // Check window and EGLConfig attributes to determine
                    // compatibility and pbuffer-texture parameters
                    Log.e(TAG, "Check window and EGLConfig attributes");
                    break;
            }
        }else
            Log.e(TAG,"create native window success eglSurface : " + eglSurface);
        EGL14.eglMakeCurrent(eglDis, eglSurface, eglSurface, eglCtx);
        EGL14.eglBindAPI(EGL14.EGL_OPENGL_ES_API);

    }


    private final float[] vertexData = { //渲染顶点坐标数据
            -1.0f,  1.0f,    //左上角
            -1.0f,  -1.0f,   //左下角
            1.0f,   1.0f,   //右下角
            1.0f,   -1.0f     //右上角
    };
    final float[] textureVertexData = { //渲染纹理坐标数据
            0.0f,   0.0f,
            0.0f,   1.0f,
            1.0f,   0.0f,
            1.0f,   1.0f,
    };

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

    private int aPositionHandle;
    private int aTextureCoordHandle;

    private int tex_y;
    private int tex_u;
    private int tex_v;
    private FloatBuffer vertexBuffer;
    private FloatBuffer textureVertexBuffer;

    /*用于将YUV数据写入文件调试*/
    private void createFileWithByte(byte[] bytes) {
        /**
         * 创建File对象,其中包含文件所在的目录以及文件的命名
         */
        File file = new File(Environment.getExternalStorageDirectory(),
                "byte_to_file.yuv");
        // 创建FileOutputStream对象
        FileOutputStream outputStream = null;
        // 创建BufferedOutputStream对象
        BufferedOutputStream bufferedOutputStream = null;
        try {
            // 如果文件存在则删除
            if (file.exists()) {
                file.delete();
            }
            // 在文件系统中根据路径创建一个新的空文件
            file.createNewFile();
            // 获取FileOutputStream对象
            outputStream = new FileOutputStream(file);
            // 获取BufferedOutputStream对象
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            // 往文件所在的缓冲输出流中写byte数据
            bufferedOutputStream.write(bytes);
            // 刷出缓冲输出流,该步很关键,要是不执行flush()方法,那么文件的内容是空的。
            bufferedOutputStream.flush();
        } catch (Exception e) {
            // 打印异常信息
            e.printStackTrace();
        } finally {
            // 关闭创建的流对象
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
        }
    }
    public void render(byte[] bytes, int screenWidth, int screenHeight, int decodeWidth, int decodeHeight, int pixWidth, int pixHeight){

//        createFileWithByte(bytes);//仅用于调试,无其它功能,暂时保留

        GLES20.glViewport(0, 0, screenWidth, screenHeight);

        /*
         * 获取着色器的属性引用id法(传入的字符串时着色器脚本中的属性名)
         * */
        if(programId == 0){
            Log.e(TAG, "create shaderUtils failed");
            return;
        }
        aPositionHandle = GLES20.glGetAttribLocation(programId, "position");//检索着色器程序的属性位置
        aTextureCoordHandle = GLES20.glGetAttribLocation(programId, "textureCoordinate");//检索着色器程序的统一位置

        tex_y = GLES20.glGetUniformLocation(programId, "tex_y");
        tex_u = GLES20.glGetUniformLocation(programId, "tex_u");
        tex_v = GLES20.glGetUniformLocation(programId, "tex_v");
        GLES20.glUseProgram(programId); //绘制时使用着色程序
        //矩阵变化
        drawFrameRender(bytes,decodeWidth, decodeHeight, pixWidth, pixHeight);

        GLES20.glVertexAttribPointer(aPositionHandle,
                2,
                GLES20.GL_FLOAT,
                false,
                2 * 4,
                vertexBuffer);
        GLES20.glEnableVertexAttribArray(aPositionHandle); //在用VertexAttribArray前必须先激活它
//        //取两个数U,V
        GLES20.glVertexAttribPointer(aTextureCoordHandle,
                2,
                GLES20.GL_FLOAT,
                false,
                2 * 4,
                textureVertexBuffer);
        GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
        // 交换显存(将surface显存和显示器的显存交换)
//        EGL14.eglMakeCurrent(eglDis, eglSurface, eglSurface, eglCtx);
        EGL14.eglSwapBuffers(eglDis,eglSurface);
        releaseBuffer();
    }
    private byte[] yuvCopy(byte[] src, int offset, int inWidth, int inHeight, byte[] dest, int outWidth, int outHeight) {
        for (int h = 0; h < inHeight; h++) {
            if (h < outHeight) {
                System.arraycopy(src, offset + h * inWidth, dest, h * outWidth, outWidth);
            }
        }
        return dest;
    }
    private void drawFrameRender(byte[] bytes,int decodeWidth, int decodeHeight, int width, int height){
        //YUV数据分离,YUV420SP(NV12和NV21) two-plane模式,即Y和UV分为两个plane UV交错存储
        //Y占byte[]的前 width*height  后面的都是UV数据,其中UV交错存储
        int [] textureIdY = new int[1];
        int [] textureIdU = new int[1];
        int [] textureIdV = new int[1];
        GLES20.glGenTextures(1, textureIdY, 0);
        GLES20.glGenTextures(1, textureIdU, 0);
        GLES20.glGenTextures(1, textureIdV, 0);
        int yBufferSize = decodeWidth * decodeHeight;
        int uBufferSize = decodeWidth / 2 * decodeHeight / 2;
        int vBufferSize = decodeWidth / 2 * decodeHeight / 2;
        byte[] dstY = new byte[yBufferSize];
        byte[] dstU = new byte[uBufferSize];
        byte[] dstV = new byte[vBufferSize];

        System.arraycopy(bytes, 0, dstY, 0,yBufferSize);
        //交叉存储的uv数据分离
        int sizeU = 0,sizeV = 0;
        for (int i = yBufferSize; i < decodeWidth * decodeHeight * 3 / 2; i++) {
            dstU[sizeU] = bytes[i];
            if (i == (decodeWidth * decodeHeight * 3 / 2 - 1)) break;
            dstV[sizeV] = bytes[i + 1];
            sizeU++;
            sizeV++;
            i++;
        }

        //Y纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIdY[0]);
        bindTexture(dstY, width, height);
        GLES20.glUniform1i(tex_y, 0);
        //U纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIdU[0]);
        bindTexture(dstU, width/2, height/2);
        GLES20.glUniform1i(tex_u, 1);
        //V纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIdV[0]);
        bindTexture(dstV, width/2, height/2);
        GLES20.glUniform1i(tex_v, 2);



        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT );
        //纹理坐标转换

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        //删除纹理数据,如果不delete,则大量的纹理数据会导致程序crash
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        GLES20.glDeleteTextures(1, textureIdY,0);
        GLES20.glDeleteTextures(1, textureIdU,0);
        GLES20.glDeleteTextures(1, textureIdV,0);
    }

    //float[]数组转化成FloatBuffer
    private FloatBuffer floatBufferUtil(float[] arr) {
        FloatBuffer mbuffer;
        // 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());

        mbuffer = qbb.asFloatBuffer();
        mbuffer.put(arr);
        mbuffer.position(0);
        return mbuffer;
    }

    private void bindTexture(byte[] buffer, int width, int height)
    {
        GLES20.glTexParameterf (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameterf (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,
                0,//GLint level
                GLES20.GL_LUMINANCE,//GLint internalformat
                width,//GLsizei width
                height,// GLsizei height,
                0,//GLint border,
                GLES20.GL_LUMINANCE,//GLenum format,
                GLES20.GL_UNSIGNED_BYTE,//GLenum type,
                ByteBuffer.wrap(buffer)//const void * pixels
        );
    }

    public EGLContext getContext() {
        return eglCtx;
    }

    public void release() {
        Log.i(TAG, "release surface and display");
        surfaceHolder = null;
        EGL14.eglMakeCurrent(eglDis, eglSurface, eglSurface, eglCtx);
        if (eglSurface != EGL14.EGL_NO_SURFACE) {
            EGL14.eglMakeCurrent(eglDis, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
            EGL14.eglDestroySurface(eglDis, eglSurface);
            eglSurface = EGL14.EGL_NO_SURFACE;
        }
        if (eglCtx != EGL14.EGL_NO_CONTEXT) {
            EGL14.eglDestroyContext(eglDis, eglCtx);
            eglCtx = EGL14.EGL_NO_CONTEXT;
        }
        if (eglDis != EGL14.EGL_NO_DISPLAY) {
            EGL14.eglTerminate(eglDis);
            eglDis = EGL14.EGL_NO_DISPLAY;
        }
        eglDis = EGL14.EGL_NO_DISPLAY;
        eglSurface = EGL14.EGL_NO_SURFACE;
        eglCtx = EGL14.EGL_NO_CONTEXT;
    }
    private void releaseBuffer(){
        vertexBuffer.clear();
        textureVertexBuffer.clear();
    }
}

Shader加载程序

package com.tutk.IOTC.openGL;

import android.opengl.GLES20;
import android.util.Log;

/*
*
*
* 着色器程序
*Create by lvyouhai
* */
public class ShaderUtils {
    private static final String TAG = "Shader";
    public static void checkGlError(String label) {
        int error;
        /**
         * 检查每一步的操作是否正确
         *
         * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误
         *
         * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器,
         *      使glAttachShader()方法, 那么这个参数就是"glAttachShader"
         */
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e(TAG, label + ": glError " + error);
            throw new RuntimeException(label + ": glError " + error);
        }
    }

    /**
     * 创建着色程序
     *
     * ① 加载顶点着色器
     * ② 加载片元着色器
     * ③ 创建着色程序
     * ④ 向着色程序中加入顶点着色器
     * ⑤ 向着色程序中加入片元着色器
     * ⑥ 链接程序
     * ⑦ 获取链接程序结果
     *
     * @param vertexSource      定点着色器脚本字符串
     * @param fragmentSource    片元着色器脚本字符串
     * @return
     */
    public  static int createProgram(String vertexSource, String fragmentSource) {
        //加载顶点着色器
        int vertexShaderIndex = LoadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShaderIndex == 0) {
            return GLES20.GL_FALSE;
        }
        //加载片元着色器
        int pixelShader = LoadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (pixelShader == 0) {
            Log.e(TAG,"pixelShader = 0");
            return GLES20.GL_FALSE;
        }

        //创建程序
        int program = GLES20.glCreateProgram();
        if (program != 0) {
            //向程序中加入顶点着色器
            GLES20.glAttachShader(program, vertexShaderIndex);
          //  checkGlError("glAttachShader");
            //向程序中加入片元着色器
            GLES20.glAttachShader(program, pixelShader);
           // checkGlError("glAttachShader");
            //链接程序
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            //获取program的连接情况
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] != GLES20.GL_TRUE) {
                //若连接失败则报错并删除程序
                Log.e(TAG, "Could not link program: ");
                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        GLES20.glDeleteShader(vertexShaderIndex);
        GLES20.glDeleteShader(pixelShader);
        return program;
    }

    /**
     * 加载着色器方法
     *
     * 流程 :
     *
     * ① 创建着色器
     * ② 加载着色器脚本
     * ③ 编译着色器
     * ④ 获取着色器编译结果
     *
     * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER)
     * @param source 着色脚本字符串
     * @return 返回的是着色器的引用, 返回值可以代表加载的着色器
     */
    public  static int LoadShader(int shaderType, String source) {
        //1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            2.如果着色器创建成功, 为创建的着色器加载脚本代码
            GLES20.glShaderSource(shader, source);
            //3.编译已经加载脚本代码的着色器
            GLES20.glCompileShader(shader);
            //存放shader的编译情况
            int[] compiled = new int[1];
            //4.获取着色器的编译情况, 如果结果为0, 说明编译失败
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0) {
                //若编译失败则显示错误日志并删除此shader
                Log.e(TAG, "Could not compile shader " + shaderType + ":");
                Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }
}

YUV数据分离,YUV420SP(NV12和NV21) two-plane模式,即Y和UV分为两个plane UV交错存储
Y占byte[]的前 widthheight 后面的都是UV数据,其中UV交错存储
PS:渲染最主要的是yuv数据的分离,需要根据解码后的yuv数据储存结构,Y数据在byte[]中,直接去width
height即可
剩下的1/2 * width * height是UV数据,存放有两种结构,UUUUVVVV,即前1/4 * width * height是U数据,后面的 1/4 * width * height是V数据;UVUVUVUV,我的程序中就是这种,分离方法可以参考代码。

4.可能遇到的问题:
a.dequeueuOutputBuffer一直return -1,这种情况一般都是解码时序有问题。在output循环取数据之后,再送下一帧。

b.渲染后没有颜色,这种情况一般都是UV数据分离出错。

MediaCodec 是 Android 提供的一个用于音视频编解码的 API,通过 MediaCodec 可以实现对 H.264 等常见视频格式的硬解码。下面是一个简单的使用 MediaCodec 解码 H.264 的示例: ```java // 创建 MediaCodec 对象 MediaCodec codec = MediaCodec.createDecoderByType("video/avc"); // 配置 MediaCodec,设置输入数据格式和输出数据 Surface MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); codec.configure(format, surface, null, 0); // 启动 MediaCodec codec.start(); // 循环读取 H.264 数据并进行解码 while (decoding) { int inputBufferIndex = codec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex); // 将 H.264 数据写入 inputBuffer 中 codec.queueInputBuffer(inputBufferIndex, 0, data.length, presentationTimeUs, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex); // 处理解码后的 YUV 数据 codec.releaseOutputBuffer(outputBufferIndex, true); } } ``` 需要注意的是,在使用 MediaCodec 解码 H.264 数据时,需要将 H.264 数据先解析成 NAL 单元,再将 NAL 单元写入到 inputBuffer 中进行解码。另外,解码后的数据是 YUV 格式的数据,需要根据实际需求进行处理。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会写代码的猴子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值