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[]中,直接去widthheight即可
剩下的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数据分离出错。