Android native 层使用opengl渲染YUV420p和NV12
《Ndk中使用Mediacode解码》
《android mediacodec 编码demo(java)》
《NDK中使用mediacodec编码h264》
《Android native 层使用opengl渲染YUV420p和NV12》
《android 使用NativeWindow渲染RGB视频》
《opengl 叠加显示文字》
《android studio 编译freeType》
《最原始的yuv图像叠加文字的实现--手动操作像素》
利用opengl进行yuv的渲染,主要原理利用显卡的加速运算,是把YUV转换成 RGBA ,然后交给gl渲染, 即opengl最终需要的数据还是 RGBA, 我们可以采用cpu软件计算的方式把 yuv转成rgb,这样计算量大,占用CPU,所以一般用opengl 利用显卡运算,把我我们的yuv数据转成RGBA.
所以这里的工作,主要在于 怎么把 yuv 利用显卡运算转换为 RGBA。
先准备点基础知识:
YUV格式
YUV420p( plane 平面格式):
YV12: y0y1y2y3y4y5y6y7 v0v1 u0u1 先平铺完y,再平铺完v,在平铺完u
YU12:y0y1y2y3y4y5y6y7 u0u1 v0v1 先平铺完y,再平铺完u,在平铺完v 这个 也叫做I420
YUV420sp( 平面+交叉)
NV12: y0y1y2y3y4y5y6y7 v0u0 v1u1 先平铺完y,再交叉存储 u和v
NV21: y0y1y2y3y4y5y6y7 u0v0 u1v1 先平铺完y,再交叉存储 v和u
opengl中纹理的颜色格式
以YUV420p YU12来举例,这计算的原理,主要是准备三个纹理, 将Y U V三个分量分别存储到一个纹理上去,然后在opengl的显卡执行程序中,将这三个分量取出进行一次矩阵运算,这个运算及时 yuv转rgb的矩阵公式,得到rgba,再显示。这里有一个注意的地方, 纹理的GLenum format
这个值有几种选择: 在gl2.h中有宏定义:
#define GL_ALPHA 0x1906
#define GL_RGB 0x1907
#define GL_RGBA 0x1908
#define GL_LUMINANCE 0x1909
#define GL_LUMINANCE_ALPHA 0x190A
在网络上搜罗了一圈,总结一下:
/*
The difference between the three single-component texture formats, GL_ALPHA, GL_LUMINANCE and GL_INTENSITY, is in the way the four-component RGBA color vector is generated. If the value for a given texel is X, then the RGBA color vector generated is:
* GL_ALPHA: RGBA = (0, 0, 0, X0)
* GL_LUMINANCE: RGBA = (X0, X0, X0, 1)
* GL_INTENSITY: RGBA = (X0, X0, X0, X0)
* GL_LUMINANCE_ALPHA: RGBA = (X0, X0, X0, X1), 这个才是二维交叉的
* GL_RGB : RGBA=(X0, X1, X2, 1),三个量交叉
* GL_RGBA : RGBA=(X0, X1, X2, X3),四个量交叉
这里用的 GL_LUMINANCE,每一纹理分量 r g b 是一样的值,都是纹理设定的数据
其实我们可以改用GL_LUMINANCE_ALPHA, 只需要一个量
*/
对于YUV420p的格式中的yuv分量,我们直接选用GL_LUMINANCE GL_ALPHA GL_INTENSITY 都可以,平铺格式。 而对于NV12 这种交叉存储的数据,就需要选用GL_LUMINANCE_ALPHA, 利用其中的任意一个 r g b分量和alph分量,可以得到 二维交叉的数据,即uv两个交叉存储。
着色器中的参数说明:
(摘自某一个博主文章https://blog.csdn.net/u012861978/article/details/97117321)
输入参数介绍:
1.着色器程序(Shader Program,图中没有画出):由 main 申明的一段程序源码或可执行文件,描述在顶点上执行的操作:如坐标变换、计算光照公式产生每个顶点颜色、计算纹理坐标。2.属性(Attribute):由 vertext array 提供的顶点数据,如空间位置,法向量,纹理坐标以及顶点颜色,属性可以理解为针对每一个顶点的输入数据。属性只在顶点着色器中才有,片元着色器中没有属性。
3.统一值(Uniforms): Uniforms保存由应用程序传递给着色器的只读常量数据。在顶点着色器中,这些数据通常是变换矩阵,光照参数,颜色等。由 uniform 修饰符修饰的变量属于全局变量,该全局性对顶点着色器与片元着色器均可见,也就是说,这两个着色器如果被连接到同一个应用程序中,它们共享同一份 uniform 全局变量集。因此如果在这两个着色器中都声明了同名的 uniform 变量,要保证这对同名变量完全相同:同名+同类型,因为它们实际是同一个变量。
4.采样器(Samplers): 一种特殊的 uniform,用于呈现纹理。sampler 可用于顶点着色器和片元着色器。
输出参数介绍:
1.可变变量(Varying):varying 变量用于存储顶点着色器的输出数据,也存储片元着色器的输入数据。varying 变量会在光栅化处理阶段被线性插值。顶点着色器如果声明了 varying 变量,它必须被传递到片元着色器中才能进一步传递到下一阶段,因此顶点着色器中声明的 varying 变量都应在片元着色器中重新声明为同名同类型的 varying 变量。(所以我们程序里在顶点着色器和片段着色器(片段或片元,叫法不同)中有同一个变量:varying vec2 vTextCoord, 是同一份变量,在顶点着色器中获取纹理输入,在片段着色器中用来计算rgb颜色。)gl_Position:在顶点着色器阶段至少应输出位置信息-即内建变量
gl_FrontFacing:为back-face culling stage阶段生成的变量,无论精选是否被禁用,该变量都会生成。
gl_PointSize:点大小。
最后绘制的位置:(顶点着色器用来确认每个点的位置)使用的三角形扇区绘制:
不明白它为什么叫顶点“着色器”,不是叫顶点生成器更好?毕竟它和颜色没什么关系,只是确定点的坐标
这里的顶点着色器,1. 没做什么运算,只是单纯原样将我们输入的数据复制到固定变量:gl_Position ,2. 将纹理坐标上下翻转了下(纹理坐标系 SW和计算机图像坐标系 XY,y轴方向刚好相反)
const char *vertexShader_ = GET_STR(
attribute vec4 aPosition;//输入的顶点坐标,会在程序指定将数据输入到该字段
attribute vec2 aTextCoord;//输入的纹理坐标,会在程序指定将数据输入到该字段
varying vec2 vTextCoord;//输出的纹理坐标
void main() {
//这里其实是将上下翻转过来
vTextCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
//直接把传入的坐标值作为传入渲染管线。gl_Position是OpenGL内置的
gl_Position = aPosition;
}
);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 一个三角形扇以第一个中心顶点作为起始,使用相邻的第三三个顶点连接起来创建第一个三角形, 接着以第二个顶点作为中心起点,第三四个顶点连接起来创建三角形,依次类推。
这里使用的顶点数据是:
static float ver[] = {
1.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f
};
对应绘制如下图,由两个三角形组成这个矩形区域。 三角形ABC + 三角形BCD
绘制的纹理颜色:(片段着色器确认最终的颜色)
这里的片段着色器程序的主要工作,就是 取纹理里面的yuv数据,然后进行一个矩阵运算转换为RGBA数据,这就是每一个片段最终的颜色数据,赋值给固定的变量:gl_FragColor
const char *fragNV12_ = GET_STR(
precision mediump float;
varying vec2 vTextCoord;
uniform sampler2D yTexture;
uniform sampler2D uvTexture;
void main(void)
{
vec3 yuv;
vec3 rgb;
yuv.x = texture2D(yTexture, vTextCoord.st).r;
yuv.y = texture2D(uvTexture, vTextCoord.st).r - 0.5;
yuv.z = texture2D(uvTexture, vTextCoord.st).a - 0.5;
rgb = mat3( 1, 1, 1,
0, -0.39465, 2.03211,
1.13983, -0.58060, 0) * yuv;
gl_FragColor = vec4(rgb, 1.0);
}
);
这里上代码:( 循环读取"/storage/emulated/0/960X544_nv12.yuv" 文件,以30帧每秒的速度渲染)
文件jni_NativeOpengl.cpp
//canok 20210528
//jni_NativeOpengl.cpp 主要实现jni转换
#include <assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"Openglfuncs.h"
#include "GlPragrame.h"
#include "logs.h"static char* jstringToChar(JNIEnv* env, jstring jstr) {char* rtn = NULL;jclass clsstring = env->FindClass("java/lang/String");//LOGD("fine String class------clsstring==NULL?%d",clsstring==NULL);jstring strencode = env->NewStringUTF("utf-8");jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);jsize alen = env->GetArrayLength(barr);jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);if (alen > 0) {rtn = (char*) malloc(alen + 1);memcpy(rtn, ba, alen);rtn[alen] = 0;}env->ReleaseByteArrayElements(barr, ba, 0);return rtn;
}void jStart(JNIEnv *env, jobject obj,jobject jsurface, jstring srcfile, int type, jint w, jint h, int fps){ANativeWindow *pWind= ANativeWindow_fromSurface(env, jsurface);char*file_in = jstringToChar(env,srcfile);//内部会开启另一个线程,并在新线程中初始化EGL环境,然后在新线程中渲染//注意,EGL初的始化的,一定要和渲染 为同一个线程 里面有个调用 eglMakeCurrent 。//如果把EGL的初始化也封装成了 jni借口由java的线程来调用,就不要在这里面单独再开启线程渲染,可以直接使用java的线程体来 执行渲染循环。mygl_start(pWind,file_in,type,w,h,fps);
}void jStop(JNIEnv *env, jobject obj){mygl_stop();
}
static JNINativeMethod gMethods[] = {{"native_start", "(Landroid/view/Surface;Ljava/lang/String;IIII)V", (void*)jStart},{"native_stop", "()V", (void*)jStop},
};static const char* const kClassPathName = "com/example/opengl_native/NativeOpengl";
static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) {jclass clazz;clazz = env->FindClass(className);if (clazz == NULL) {return JNI_FALSE;}if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {return JNI_FALSE;}return JNI_TRUE;
}
// This function only registers the native methods
static int registerFunctios(JNIEnv *env)
{ALOGD("register [%s]%d",__FUNCTION__,__LINE__);return registerNativeMethods(env,kClassPathName, gMethods, sizeof(gMethods)/sizeof(gMethods[0]));
}jint JNI_OnLoad(JavaVM* vm, void* reserved)
{ALOGD("onloader");JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {ALOGD("ERROR: GetEnv failed\n");goto bail;}assert(env != NULL);if (registerFunctios(env) < 0) {ALOGE(" onloader ERROR: MediaPlayer native registration failed\n");goto bail;}ALOGD("onloader register ok ![%s]%d",__FUNCTION__,__LINE__);result = JNI_VERSION_1_4;bail:return result;
}
文件Openglfuncs.cpp
//canok 20210528
//文件openglfuncs.cpp 主要初始化egl环境,读文件进行渲染
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "logs.h"
#include "GlPragrame.h"
#include "Openglfuncs.h"
#define MIN(x,y) (x)<(y)?(x):(y)#define DEBUG_FROME_FILE 1
EGLDisplay megldisplay =NULL;
EGLSurface meglSurface =NULL;
EGLContext meglContext=NULL;
int mbRun=0;
void initGles(){// 1 EGL 初始化EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);if(display == EGL_NO_DISPLAY){ALOGE("eglGetDisplay failed %d", eglGetError());return;}megldisplay = display;if(!eglInitialize(display,NULL,NULL)){ALOGE("eglInitialize failed %d", eglGetError());}//2配置displayEGLConfig config;EGLint configNum;EGLint configSpec[] = {EGL_RED_SIZE,8,EGL_GREEN_SIZE, 8,EGL_BLUE_SIZE, 8,EGL_ALPHA_SIZE,8,EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT,EGL_SURFACE_TYPE,EGL_WINDOW_BIT,EGL_NONE};if(EGL_TRUE != eglChooseConfig(display, configSpec, &config, 1, &configNum)){ALOGD("eglChooseConfig failed!");return;}//3 EGLcontext 创建上下文const EGLint ctxAttr[] = {EGL_CONTEXT_CLIENT_VERSION, 2,EGL_NONE};EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);if(context == EGL_NO_CONTEXT){ALOGD("eglCreateContext failed!");return;}meglContext=context;//4创建EGLsurface,把 java端传入的surface和EGL关联起来EGLSurface eglsurface = eglCreateWindowSurface(display, config, global_Context.nwin, 0);if(eglsurface == EGL_NO_SURFACE){ALOGD("eglCreateWindowSurface failed!");return;}meglSurface=eglsurface;if(EGL_TRUE != eglMakeCurrent(display, eglsurface, eglsurface, context)){ALOGD("eglMakeCurrent failed!");return ;}glClearColor(0.0f,0.0f,0.0f,1.0f);glClear(GL_COLOR_BUFFER_BIT);eglSwapBuffers(megldisplay, meglSurface);
}void *renderthread(void * prame){
#if DEBUG_FROME_FILE//第一步搭建ES环境initGles();ALOGD("mygl_init intglescontext ok ");//第二步 创建显卡可执行程序createprograme();ALOGD("mygl_init createprograme ok %d",global_Context.type);//waring!!!!!!!!//glViewport(0,0,WIDTH,HEIGHT);FILE *fp = fopen(global_Context.file_in,"r");if(fp ==NULL){ALOGD("fopen failed!");return NULL;}global_Context.buflen=global_Context.mW*global_Context.mH*3/2;global_Context.buf = (unsigned char*)malloc( global_Context.buflen);int ret =0;while(mbRun){//这里绘制if( (ret = fread(global_Context.buf,1,global_Context.buflen,fp)) ==global_Context.buflen ){drawFrame(global_Context.buf,global_Context.buflen);eglSwapBuffers(megldisplay, meglSurface);usleep(1000*1000/global_Context.mfps);}else{//循环播放fseek(fp,0,SEEK_SET);}}//销毁if(megldisplay!=NULL && meglSurface!=NULL && meglContext!=NULL){eglDestroySurface(megldisplay,meglSurface);eglDestroyContext(megldisplay,meglContext);}fclose(fp);free(global_Context.file_in);free(global_Context.buf);
#elseinitGles();ALOGD(" intglescontext ok ");createprograme();ALOGD(" createprograme ok ");global_Context.buflen=global_Context.mW*global_Context.mH*3/2;global_Context.buf = (unsigned char*)malloc( global_Context.buflen);if(global_Context.buf == NULL){ALOGE("err to malloc!");return NULL;}while(mbRun){//条件等待pthread_mutex_lock(&global_Context.mCondMutex);pthread_cond_wait(&global_Context.mCond,&global_Context.mCondMutex);pthread_mutex_unlock(&global_Context.mCondMutex);pthread_mutex_lock(&global_Context.bufMutex);do {//渲染,期间不允许同时修改drawFrame(global_Context.buf, global_Context.buflen);eglSwapBuffers(megldisplay, meglSurface);}while(0);pthread_mutex_unlock(&global_Context.bufMutex);}//销毁if(megldisplay!=NULL && meglSurface!=NULL && meglContext!=NULL){eglDestroySurface(megldisplay,meglSurface);eglDestroyContext(megldisplay,meglContext);}free(global_Context.buf);
#endifpthread_exit(NULL);return NULL;
}
void mygl_start(ANativeWindow *pWnid, char*file_in,int type, int w, int h,int fps){global_Context.file_in = file_in;global_Context.type = type;global_Context.nwin = pWnid;global_Context.mW = w;global_Context.mH = h;global_Context.mfps =fps;ALOGD("start run");//创建渲染程序:if(mbRun){ALOGE("had run, return");}mbRun =true;int ret =0;if(0 != (ret = pthread_create(&global_Context.mThread,NULL,renderthread,NULL))){ALOGE("pthread_create erro");}//pthread_detach(pd);
}
void mygl_render(unsigned char *buf,int size){pthread_mutex_lock(&global_Context.bufMutex);do {//拷贝,期间不允许同时修改memcpy(global_Context.buf,buf,MIN(size,global_Context.buflen));}while(0);pthread_mutex_unlock(&global_Context.bufMutex);//释放条件pthread_mutex_lock(&global_Context.mCondMutex);pthread_cond_signal(&global_Context.mCond);pthread_mutex_unlock(&global_Context.mCondMutex);}
void mygl_stop(){mbRun = false;//释放条件pthread_mutex_lock(&global_Context.mCondMutex);pthread_cond_signal(&global_Context.mCond);pthread_mutex_unlock(&global_Context.mCondMutex);//等待结束,避免出现 FORTIFY: pthread_mutex_lock called on a destroyed mutexpthread_join(global_Context.mThread,NULL);
}
文件GlPragrame.cpp
//canok 20210528
//GlPragrame.cpp 主要实现 gl程序
//canok 2021.0528
#include <stdlib.h>
#include "GlPragrame.h"
#include "Openglfuncs.h"
#include "logs.h"
#define GET_STR(x) #x
struct S_CONTEXT global_Context = {.bufMutex=PTHREAD_MUTEX_INITIALIZER,.mCondMutex=PTHREAD_MUTEX_INITIALIZER,
.mCond=PTHREAD_COND_INITIALIZER};const char *vertexShader_ = GET_STR(
attribute vec4 aPosition;//输入的顶点坐标,会在程序指定将数据输入到该字段
attribute vec2 aTextCoord;//输入的纹理坐标,会在程序指定将数据输入到该字段
varying vec2 vTextCoord;//输出的纹理坐标
void main() {//这里其实是将上下翻转过来vTextCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);//直接把传入的坐标值作为传入渲染管线。gl_Position是OpenGL内置的gl_Position = aPosition;
}
);
/*
The difference between the three single-component texture formats, GL_ALPHA, GL_LUMINANCE and GL_INTENSITY, is in the way the four-component RGBA color vector is generated. If the value for a given texel is X, then the RGBA color vector generated is:* GL_ALPHA: RGBA = (0, 0, 0, X0)* GL_LUMINANCE: RGBA = (X0, X0, X0, 1)* GL_INTENSITY: RGBA = (X0, X0, X0, X0)* GL_LUMINANCE_ALPHA: RGBA = (X0, X0, X0, X1), 这个才是二维交叉的* GL_RGB : RGBA=(X0, X1, X2, 1),三个量交叉* GL_RGBA : RGBA=(X0, X1, X2, X3),四个量交叉这里用的 GL_LUMINANCE,每一纹理分量 r g b 是一样的值,都是纹理设定的数据其实我们可以改用GL_LUMINANCE_ALPHA, 只需要一个量这里整个的原理,即把YUV420S 每一个分量当做一个纹理,然后在片原着色器中利用显卡的高速运算,通过 yuv转rgb 的矩阵公式,把三个纹理分量的值计算转换成rgb接着,然后画这个rgba 的点。
*/const char *fragYUV420P_ = GET_STR(precision mediump float;varying vec2 vTextCoord;//输入的yuv三个纹理uniform sampler2D yTexture;//采样器uniform sampler2D uTexture;//采样器uniform sampler2D vTexture;//采样器void main() {vec3 yuv;vec3 rgb;//分别取yuv各个分量的采样纹理//下面是一个 yuv转 rgb的矩阵运算// 纹理的 r g b相等,都是一个值/**矩阵公式*【】x [[yuv] -[0,0.5,0.5]]*/yuv.x = texture2D(yTexture, vTextCoord).r;yuv.y = texture2D(uTexture, vTextCoord).r - 0.5;yuv.z = texture2D(vTexture, vTextCoord).r - 0.5;rgb = mat3(1.0, 1.0, 1.0,0.0, -0.39465, 2.03211,1.13983, -0.5806, 0.0) * yuv;//gl_FragColor是OpenGL内置的gl_FragColor = vec4(rgb, 1.0);}
);const char *fragNV12_ = GET_STR(precision mediump float;varying vec2 vTextCoord;uniform sampler2D yTexture;uniform sampler2D uvTexture;void main(void){vec3 yuv; vec3 rgb; yuv.x = texture2D(yTexture, vTextCoord.st).r; yuv.y = texture2D(uvTexture, vTextCoord.st).r - 0.5; yuv.z = texture2D(uvTexture, vTextCoord.st).a - 0.5; rgb = mat3( 1, 1, 1, 0, -0.39465, 2.03211, 1.13983, -0.58060, 0) * yuv; gl_FragColor = vec4(rgb, 1.0); }
);/*** 加载 着色器* @param type 着色器类型* @param shaderSrc 着色源码* @return*/
GLuint LoadShader(GLenum type, const char *shaderSrc) {GLuint shader;GLint compiled;shader = glCreateShader(type); // 创建 着色器 句柄if (shader == 0) {ALOGE("create shader error");return 0;}// 装载 着色器源码glShaderSource(shader, 1, &shaderSrc, 0);// 编译着色器glCompileShader(shader);// 检测编译状态glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);if (!compiled) {GLint infoLen = 0;// 获取日志长度glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);if (infoLen > 1) {GLchar *infoLog = (GLchar *)(malloc(sizeof(GLchar) * infoLen));// 获取日志 信息glGetShaderInfoLog(shader, infoLen, 0, infoLog);ALOGE("%s", infoLog);free(infoLog);}glDeleteShader(shader);return 0;}return shader;
}GLuint LoadProgramYUV(int type){GLint vertexShader = LoadShader(GL_VERTEX_SHADER,vertexShader_);GLint fragShader;if(TYPE_YUV420SP_NV12 == type){fragShader = LoadShader(GL_FRAGMENT_SHADER,fragNV12_);}else{//默认用yuv420pfragShader = LoadShader(GL_FRAGMENT_SHADER,fragYUV420P_);}// 创建渲染程序GLint program = glCreateProgram();if (program == 0){ALOGE("YUV : CreateProgram failure");glDeleteShader(vertexShader);glDeleteShader(fragShader);return 0;}// 先渲染程序中 加入着色器glAttachShader(program,vertexShader);glAttachShader(program,fragShader);// 链接 程序glLinkProgram(program);GLint status = 0;glGetProgramiv(program,GL_LINK_STATUS,&status);if (status == 0){ALOGE("glLinkProgram failure");glDeleteProgram(program);return 0;}global_Context.glProgram = program;glUseProgram(program);return 1;}void RenderYUVConfig(uint width, uint height, int type) {static float ver[] = {1.0f, -1.0f, 0.0f,-1.0f, -1.0f, 0.0f,1.0f, 1.0f, 0.0f,-1.0f, 1.0f, 0.0f};GLuint apos = (GLuint)(glGetAttribLocation(global_Context.glProgram, "aPosition"));glEnableVertexAttribArray(apos);glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 0, ver);//加入纹理坐标数据static float fragment[] = {1.0f, 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f, 1.0f};GLuint aTex = (GLuint)(glGetAttribLocation(global_Context.glProgram, "aTextCoord"));glEnableVertexAttribArray(aTex);glVertexAttribPointer(aTex, 2, GL_FLOAT, GL_FALSE, 0, fragment);ALOGD("can_ok[%s%d] type:%d",__FUNCTION__,__LINE__,type);if(TYPE_YUV420SP_NV12 == type){ALOGD("can_ok[%s%d] type:%d",__FUNCTION__,__LINE__,type);glUniform1i(glGetUniformLocation(global_Context.glProgram, "yTexture"), 0);glUniform1i(glGetUniformLocation(global_Context.glProgram, "uvTexture"), 1);glGenTextures(2, global_Context.mTextures);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexImage2D(GL_TEXTURE_2D,0,GL_LUMINANCE,width,height,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,NULL );glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexImage2D(GL_TEXTURE_2D,0,GL_LUMINANCE_ALPHA, width / 2,height / 2,0, GL_LUMINANCE_ALPHA,GL_UNSIGNED_BYTE,NULL );}else{glUniform1i(glGetUniformLocation(global_Context.glProgram, "yTexture"), 0);glUniform1i(glGetUniformLocation(global_Context.glProgram, "uTexture"), 1);glUniform1i(glGetUniformLocation(global_Context.glProgram, "vTexture"), 2);glGenTextures(3, global_Context.mTextures);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexImage2D(GL_TEXTURE_2D,0,GL_LUMINANCE,width,height,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,NULL);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexImage2D(GL_TEXTURE_2D,0,GL_LUMINANCE,width / 2,height / 2,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,NULL);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[2]);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexImage2D(GL_TEXTURE_2D,0,GL_LUMINANCE,width / 2,height / 2,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,NULL);}
}//平面格式
void RenderYUV420P(GLuint width, GLuint height, unsigned char *buf) {//YglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);glTexSubImage2D(GL_TEXTURE_2D, 0,0, 0,width, height,GL_LUMINANCE, GL_UNSIGNED_BYTE,buf);//UglActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);glTexSubImage2D(GL_TEXTURE_2D, 0,0, 0,width / 2, height / 2,GL_LUMINANCE, GL_UNSIGNED_BYTE,buf+width*height);//VglActiveTexture(GL_TEXTURE2);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[2]);glTexSubImage2D(GL_TEXTURE_2D, 0,0, 0,width / 2, height / 2,GL_LUMINANCE, GL_UNSIGNED_BYTE,buf+width*height*5/4);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//eglSwapBuffers(global_Context.eglDisplay, global_Context.eglSurface);}void RenderNV12(GLuint width, GLuint height, unsigned char * buf){//YglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[0]);glTexSubImage2D(GL_TEXTURE_2D, 0,0, 0,width, height,GL_LUMINANCE,GL_UNSIGNED_BYTE,buf);//UVglActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, global_Context.mTextures[1]);glTexSubImage2D(GL_TEXTURE_2D, 0,0, 0,width/2, height / 2,GL_LUMINANCE_ALPHA,GL_UNSIGNED_BYTE,buf+width*height);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
void createprograme(){LoadProgramYUV(global_Context.type);RenderYUVConfig(global_Context.mW,global_Context.mH,global_Context.type);
}
void drawFrame(unsigned char* buf, int size) {if(global_Context.type == TYPE_YUV420SP_NV12){ALOGD("[%s%d] render NV12 %dx%d",__FUNCTION__,__LINE__,global_Context.mW,global_Context.mH);RenderNV12(global_Context.mW,global_Context.mH,buf);}else{ALOGD("[%s%d]render YUV420P %dx%d",__FUNCTION__,__LINE__,global_Context.mW,global_Context.mH);RenderYUV420P(global_Context.mW,global_Context.mH,buf);}
}void changeLayout(int width, int height){glViewport(0,0,width,height);
}
//canok 20210528
//头文件:
logs.h
#ifndef __MY_LOGS_HEADER__
#define __MY_LOGS_HEADER__
#ifdef __cplusplus
extern "C" {
#endif
#include <android/log.h>
// 宏定义类似java 层的定义,不同级别的Log LOGI, LOGD, LOGW, LOGE, LOGF。 对就Java中的 Log.i log.d
#define LOG_TAG "hpc -- JNILOG" // 这个是自定义的LOG的标识
//#undef LOG // 取消默认的LOG
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define ALOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)#ifdef __cplusplus
}
#endif#endifOpenGLfuncs.h
#ifndef __OPENGLFUNCS_HH
#define __OPENGLFUNCS_HH
#include<jni.h>
#include<android/native_window.h>
#include<android/native_window_jni.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include<EGL/egl.h>
#include<EGL/eglext.h>#define TYPE_YUV420P 19
#define TYPE_YUV420SP_NV12 21
void mygl_start(ANativeWindow *pWnid,char*file_in, int type,int w, int h,int fps);
void mygl_stop();//下面函数供其它模块输入渲染数据
void mygl_render(unsigned char *buf,int size);
#endifGlprograme.h
#ifndef __GLPRAGRAME2__H__
#define __GLPRAGRAME2__H__
#include <jni.h>
#include<EGL/egl.h>
#include<EGL/eglext.h>
#include <android/log.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>#include <pthread.h>
#include <unistd.h>
struct S_CONTEXT{GLint glProgram;GLuint mTextures[3];int mW;int mH;int type;int mfps;ANativeWindow *nwin ;unsigned char* buf;int buflen;pthread_mutex_t bufMutex;pthread_mutex_t mCondMutex;pthread_cond_t mCond;pthread_t mThread;char *file_in;
} ;
extern struct S_CONTEXT global_Context;
void createprograme();
void drawFrame(unsigned char* buf, int size) ;
#endif
添加上java 代码:
package com.example.opengl_native;import android.util.Log;
import android.view.Surface;
public class NativeOpengl {private static final String TAG = "NativeOpengl";private Surface mSurface;NativeOpengl(){}private native void native_start(Surface surface,String file_in,int type,int w, int h, int fps);private native void native_stop();static {System.loadLibrary("NativeOpengl");}public void play(Surface surface,String file_in,int type,int w, int h,int fps){String colortype="unsupport_type";if(type ==21){colortype = "NV12_YUV420SP";}else if(type==19){colortype = "YUV420P";}Log.d(TAG, "play: "+surface+"file_in:"+file_in+" type,"+colortype+",size:"+w+"x"+h+"@"+fps);mSurface = surface;native_start(surface,file_in,type,w,h,fps);}}
package com.example.opengl_native;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private Button mButtonPlay;private SurfaceView mView;private NativeOpengl mOpengl;private Surface mSurface;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);verifyStoragePermissions(this);mView = findViewById(R.id.surface);SurfaceHolder holder = mView.getHolder();holder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {// TODO Auto-generated method stubLog.i("SURFACE","destroyed");}@Overridepublic void surfaceCreated(SurfaceHolder holder) {// TODO Auto-generated method stubLog.i("SURFACE","create");mSurface = holder.getSurface();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {// TODO Auto-generated method stub}});mButtonPlay = findViewById(R.id.button);mButtonPlay.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if(mSurface!=null && mOpengl==null) {mOpengl = new NativeOpengl();mOpengl.play(mSurface,"/storage/emulated/0/960X544_nv12.yuv",21,960,544,30);}}});}private static final int REQUEST_EXTERNAL_STORAGE = 1;private static String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};public static boolean verifyStoragePermissions(Activity activity) {if(Build.VERSION.SDK_INT < 23){Log.d(TAG, "verifyStoragePermissions: <23");return true;}// Check if we have write permissionint permission = ActivityCompat.checkSelfPermission(activity,Manifest.permission.RECORD_AUDIO);if (permission != PackageManager.PERMISSION_GRANTED) {// We don't have permission so prompt the userActivityCompat.requestPermissions(activity,PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);return false;}else{return true;}}
}
Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := NativeOpengl
LOCAL_SRC_FILES := Openglfuncs.cpp \GlPragrame.cpp \jni_NativeOpengl.cppLOCAL_LDLIBS += -lGLESv2 -lEGL \-llog -landroid
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPESinclude $(BUILD_SHARED_LIBRARY)
布局:
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:layout_editor_absoluteX="73dp"tools:layout_editor_absoluteY="168dp"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"tools:layout_editor_absoluteX="73dp"><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="播放" /></LinearLayout><SurfaceViewandroid:id="@+id/surface"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>清单文件:
package="com.example.opengl_native"><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:requestLegacyExternalStorage="true"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.Opengl_native"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>
Android native 层使用opengl渲染YUV420p和NV12相关推荐
- android transact,Android Native层Binder.transact()函数调用 Binder.onTransact() 函数失败分析...
Q:Android Native层Binder.transact()函数调用 Binder.onTransact() 函数失败? 在Android Native层调用Camera.h中的api实现一个 ...
- Android Native层
android native层是 相对于Java 层的底层,一般用c++开发 Java框架层就是常说的Framework,这层里东西很多也很复杂,比如说主要的一些系统服务如ActivityManage ...
- android native层进程通信
我们需要在android native层实现两个进程间的通信,这两个进程一个是native c 进程,一个是apk,两者需要传递一张几百K的图片,natice c 程序写该图片,apk读该图片,而且, ...
- Android平台上基于OpenGl渲染yuv视频
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 更多音视频开发文章,请看:音视频开发专栏 介绍一个自己刚出炉的音视频播放录制开源项目 前言 这是我音视频专栏的第一篇实例解析,也算是 ...
- android native java_在Android Native层中创建Java虚拟机实例
前言 Android应用中JNI代码,是作为本地方法运行的.而大部分情况下,这些JNI方法均需要传递Dalvik虚拟机实例作为第一个参数.例如,你需要用虚拟机实例来创建jstring和其他的Java对 ...
- 在Android Native层中创建Java虚拟机实例
前言 Android应用中JNI代码,是作为本地方法运行的.而大部分情况下,这些JNI方法均需要传递Dalvik虚拟机实例作为第一个参数.例如,你需要用虚拟机实例来创建jstring和其他的Java对 ...
- Android native层Hander原理分析
目录 概述 源 自实现 下面简单叙述其原理: 如何使用: 注意问题: 概述 本篇探究 Android framework native层多媒体库中的 AHandler+ALooper+AMessage ...
- Android Native层错误调试
前言 Android系统对于Native(C/C++)应用程序的调试手段比单纯的linux系统coredump文件与gdb结合调试的手段.但是Android系统的天然不支持这种调试方式,其在内核中就没 ...
- android 渲染yuv数据,Android opengl渲染yuv420例子
[实例简介] Android下使用OpenGL渲染yuv420p图像并显示.例子中提供了两种类型,一种使用GLSurfaceView在onDrawframe中调用native方法绘制,另外一种使用EG ...
最新文章
- Linux下OpenSSL的安装与使用
- 阅读react-redux源码 - 零
- Android 数据库综述(二) 程序计算器与信号量来处理多线程并发问题
- java财务对账系统设计_对账系统设计
- 细说php完美分页类
- 【软件工程】二、需求分析——怎么提需求?,怎么写需求?
- 算法音乐往事:二次元女神“初音未来”诞生记
- android toast样式 最新,Android 五种不同样式Toast
- 从百度指数到微信指数,我们正进入“数据世界”
- MSP430F5529-基于ATK-IMU901角度传感器在UART的串口通信于数据显示
- 面试官:为什么选择做测试?我对测试特别感兴趣,我喜欢软件测试
- windows7 32位系统下好用的一款视频制作工具--Windows live影音制作
- LINUX常用的字符串,常用linux 命令 -字符串相关
- iOS 的一种设计模式 类别 catagory
- 一台手机=半套房?VERTU唐卡手机拍出48.3万
- 农业种植大数据平台构建
- 毕业前三年如何拿到好绩效
- YTU 2451: 股市风云
- 使用钉钉创建企业内部机器人
- 工业和信息化部关于开展纵深推进APP侵害用户权益专项整治行动的通知(工信部信管函〔2020〕164号)