之前 android studio搭建简单jni层的opengl开发框架讲到的是在上层render的三个回调函数中写jni函数,从而在jni层调用opengl的绘制,但是在做播放器的时候,需要用opengl来渲染 每一帧,而底层解码后,再渲染,将渲染放在底层,这样播放器的整个框架都在底层,上层只是UI的一些显示操作,而且 如果在render的onFrame中直接来显示帧,在暂停或者seek的时候由于 glsurfaceview的双缓冲机制,容易导致画面2帧之间来回跳动,不好控制。
上层g.lsurfaceview部分
上层继承glSurfaceView,实现callback接口 和 renderer接口
package com.example.opengltest;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class LGlsurfaceView extends GLSurfaceView implements SurfaceHolder.Callback, GLSurfaceView.Renderer {//
static {
System.loadLibrary("lammyVideoPlayer");
}
public LGlsurfaceView(Context context) {
super(context);
setRenderer(this);
}
public void surfaceCreated(SurfaceHolder holder){
initEgl(holder.getSurface());
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height){
}
public void surfaceDestroyed(SurfaceHolder holder){
closeEgl();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
@Override
public void onDrawFrame(GL10 gl) {
}
native private void initEgl(Surface surface);
native private void closeEgl();
}
initEgl是底层实现 EGL环境的搭建
closeEgl是关闭底层EGL渲染环境
EGL创建和关闭的类和源文件
//
// Created by lammy on 2019/4/20.
//
#ifndef LAMMYOPENGLFFMPEGVIDEOPLAYER_2_LEGL_H
#define LAMMYOPENGLFFMPEGVIDEOPLAYER_2_LEGL_H
#include <mutex>
#include <EGL/egl.h>
class LEGL {
public:
EGLDisplay display = EGL_NO_DISPLAY;
EGLSurface surface = EGL_NO_SURFACE;
EGLContext context = EGL_NO_CONTEXT;
std::mutex mux;
public:
bool Init(void *win);
void Close();
void Draw();
static LEGL *Get();
};
#endif //LAMMYOPENGLFFMPEGVIDEOPLAYER_2_LEGL_H
//
// Created by lammy on 2019/4/20.
//
#include <Log.h>
#include <android/native_window_jni.h>
#include <ggl.h>
#include "LEGL.h"
void LEGL::Draw()
{
mux.lock();
if(display == EGL_NO_DISPLAY || surface == EGL_NO_SURFACE)
{
LOGE("draw..EGL_NO_DISPLAY.....................................................1111111111111111.");
mux.unlock();
return;
}
glFinish();
bool re = eglSwapBuffers(display,surface);
if(re == EGL_FALSE)
{
LOGE("draw failed ......");
}
LOGE("draw success ......");
mux.unlock();
}
void LEGL::Close()
{
mux.lock();
if(display == EGL_NO_DISPLAY)
{
mux.unlock();
return;
}
eglMakeCurrent(display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT);
if(surface != EGL_NO_SURFACE)
eglDestroySurface(display,surface);
if(context != EGL_NO_CONTEXT)
eglDestroyContext(display,context);
eglTerminate(display);
display = EGL_NO_DISPLAY;
surface = EGL_NO_SURFACE;
context = EGL_NO_CONTEXT;
mux.unlock();
}
bool LEGL::Init(void *win)
{
Close();
//初始化EGL
mux.lock();
/*************************EGL环境创建*********************/
//1、 display 获得显示的对象
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(display == EGL_NO_DISPLAY)
{
LOGE("display failed");
mux.unlock();
return false;
}
//1.2初始化,第二 和 三个参数,是主版本号,次版本号 EGL版本号
if(EGL_TRUE != eglInitialize(display, 0 , 0))
{
LOGE("eglInitialize failed");
mux.unlock();
return false;
}
//2、surface
//2.1surface窗口配置
EGLConfig eglConfig = 0;// 输出config项
EGLint configNum = 0;//输出config数量
EGLint configSpec[]={
EGL_RED_SIZE,8,
EGL_GREEN_SIZE,8,
EGL_BLUE_SIZE,8,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE// 结尾符
};
if(EGL_TRUE != eglChooseConfig(display, configSpec, &eglConfig,1,&configNum))
{
LOGE("eglChooseConfig failed");
mux.unlock();
return false;
}
// 创建 surface
surface = eglCreateWindowSurface(display, eglConfig, ( ANativeWindow *)win,0 );// 最后一个是版本信息,这里是默认的
if(surface == EGL_NO_SURFACE){
LOGE("eglCreateWindowSurface failed");
mux.unlock();
return false;
}
// 创建关联上下文 EGL_NO_CONTEXT 这个参数是 多个设备共享 上下文时候,传入context,这里用不到,传EGL_NO_CONTEXT
const EGLint ctxATTR[]{
EGL_CONTEXT_CLIENT_VERSION, 2 , EGL_NONE
};
context = eglCreateContext(display, eglConfig, EGL_NO_CONTEXT,ctxATTR);
if(context == EGL_NO_CONTEXT)
{
LOGE("eglCreateContext failed");
mux.unlock();
return false;
}
// 将上下文,和 surface 和 display 关联。 并且在当前线程中启动当前的渲染环境
if(EGL_TRUE != eglMakeCurrent(display, surface, surface,context))// 2、3 2个surface 一个读 一个写,双缓冲
{
LOGE("eglMakeCurrent failed");
mux.unlock();
return false;
}
LOGW("RGL init success");
mux.unlock();
return true;
}
LEGL *LEGL::Get()
{
static LEGL egl;
return &egl;
}
根据上层传入的surfaceview 创建nativeWindow,然后利用window创建:
EGLDisplay display = EGL_NO_DISPLAY;
EGLSurface surface = EGL_NO_SURFACE;
EGLContext context = EGL_NO_CONTEXT;
然后创建末端启用创建的环境,即将context 和 surface 和 display结合起来。
调用EGL
在surfaceview Surface创建后,初始化 opengl的shader 创建program 绘制,绘制完毕后,调用eglSwapBuffers(display,surface); 来将opengl绘制的纹理渲染到 窗口。在上面的EGL中已经封装在Draw函数内部了。
node:渲染的线程 必须 与 EGL的创建,即init必须在同一个线程,否则就会绘制失效。这里主要是因为:eglMakeCurrent(display, surface, surface,context)在里面调用了,当然可以在别的线程先初始化好 context 、display 和context ,然后再渲染线程中 绘制前就调用 eglMakeCurrent。
JNI层调用例子:
bool isExit = false;
void test(ANativeWindow *win)
{
// LEGL::Get()->Init(win);
if(EGL_TRUE != eglMakeCurrent(LEGL::Get()->display, LEGL::Get()->surface,
LEGL::Get()->surface,LEGL::Get()->context))// 2、3 2个surface 一个读 一个写,双缓冲
{
LOGE("eglMakeCurrent failed");
return;
}
glProgram = new GLProgram();
glProgram->init();
while(!isExit){
LOGE("draw ing.............");
glProgram->Draw();
LEGL::Get()->Draw();
}
LOGE("draw close.............");
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_opengltest_LGlsurfaceView_initEgl(JNIEnv *env, jobject instance, jobject surface)
{
// TODO
LOGE("initEgl.............");
isExit = false;
ANativeWindow *win = ANativeWindow_fromSurface(env,surface);
LEGL::Get()->Init(win);
std::thread a(test,win);
a.detach();
}
如果不在渲染前 调用eglMakeCurrent,是无法绘制出来的。
下面给出我的demo,欢迎各位老铁下载。