SurfaceView+EGL+OpenGL ES+ffmpeg播放视频并解决黑屏不显示画面的问题
一、概述
使用纯C++开发的方式实现EGL、OpenGL ES、ffmpeg部分。Java/kotlin部分只需要传递一个文件url地址即可实现视频的播放。
相关的坑以及解决的办法:
- 编译正常但是画面不显示
- Surface和EGL没有绑定。解决办法如下:
winsurface = eglCreateWindowSurface(display, config, nwin, 0); if (winsurface == EGL_NO_SURFACE) { LOG_D("eglCreateWindowSurface failed!"); return; }
- EGL和OpenGL ES没有绑定,解决办法如下:
if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) { LOG_D("eglMakeCurrent failed!"); return; }
- EGL和OpenGL ES没有在同一个线程中绑定。这个问题很容易被忽略,需要提高警惕。解决办法如下:
void YEglSampleGlesWindow::RenderView() { ///切换opengl es线程 //绑定Display窗口和OpenGL ES yeglContextManager.MakeCurrent(); LOG_D("EGL Init Success!"); eglVideoShader.Init(width, height);
一定要在你的Shader的渲染线程中调用如下方法进行绑定。
if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) { LOG_D("eglMakeCurrent failed!"); return; }
- EGL未调用前后端buffer切换。解决办法如下。也是在渲染线程中调用如下方法:
//窗口显示,此方法不调用画面就不会显示,即使之间的99%的步骤都做对了。 eglSwapBuffers(display, winsurface);
- Surface和EGL没有绑定。解决办法如下:
二、代码示例
代码部分介绍:
- 自定义SurfaceView关键代码
- 继承SurfaceView并重写SurfaceHolder.Callback
class YEGLView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs), Runnable, SurfaceHolder.Callback
- 重写SurfaceHolder.Callback类并在其生命周期回调函数中调用jni函数
init { holder.addCallback(this) } override fun surfaceCreated(holder: SurfaceHolder) { Log.e(TAG, "surfaceCreated") mSurface = holder.surface Thread(this).start() } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { Log.e(TAG, "surfaceChanged") } override fun surfaceDestroyed(holder: SurfaceHolder) { Log.e(TAG, "surfaceDestroyed") releaseEGLGLES() }
- 继承SurfaceView并重写SurfaceHolder.Callback
- JNI部分关键代码
const char *m_url = env->GetStringUTFChars(url, 0); yEglSampleGlesWindow.Init(g_jvm, surface); yEglSampleGlesWindow.Start(m_url); env->ReleaseStringUTFChars(url, m_url);//销毁url
- EGL工具类关键代码
- 初始化
void YEGLContextManager::Init(JavaVM *vm, jobject surface) { this->_vm = vm; JNIEnv *env = nullptr; jint result = _vm->GetEnv((void **) &env, JNI_VERSION_1_6); if (result == JNI_EDETACHED) { // 线程未附加到JVM,需要先附加 if (_vm->AttachCurrentThread(&env, nullptr) != JNI_OK) { LOG_E("附件JNI_ENV失败"); return; } } //1 获取原始窗口 nwin = ANativeWindow_fromSurface(env, surface); //////////////////// ///EGL //1 EGL display创建和初始化 display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (display == EGL_NO_DISPLAY) { LOG_D("eglGetDisplay failed!"); return; } if (EGL_TRUE != eglInitialize(display, 0, 0)) { LOG_D("eglInitialize failed!"); return; } //2 surface //2-1 surface窗口配置 //输出配置 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, &config, 1, &configNum)) { LOG_D("eglChooseConfig failed!"); return; } //创建surface winsurface = eglCreateWindowSurface(display, config, nwin, 0); if (winsurface == EGL_NO_SURFACE) { LOG_D("eglCreateWindowSurface failed!"); return; } //3 context 创建关联的上下文 const EGLint ctxAttr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr); if (context == EGL_NO_CONTEXT) { LOG_D("eglCreateContext failed!"); return; } }
- EGL绑定OpenGL ES线程
void YEGLContextManager::MakeCurrent() { if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) { LOG_D("eglMakeCurrent failed!"); return; } }
- 显示窗口
void YEGLContextManager::ShowWindow() { //窗口显示 eglSwapBuffers(display, winsurface); }
- 初始化
- 渲染类的关键代码
- 在渲染线程中调用EGL的MakeCurrent()方法绑定OpenGL ES线程环境
yeglContextManager.MakeCurrent();
- 初始化Shader
eglVideoShader.Init(width, height);
- 循环渲染
AVPacket pkt; AVFrame *frame = av_frame_alloc(); while (isRunning) { int d_result = demuxer.Read(&pkt); if (!decoder.Send(&pkt)) { av_packet_unref(&pkt); MSleep(10); continue; } while (decoder.Receive(frame)) { auto f = av_frame_alloc(); av_frame_ref(f, frame);//引用计数加1 //渲染shader eglVideoShader.Render(f, [this]() { }); yeglContextManager.ShowWindow(); } }
- 在渲染线程中调用EGL的MakeCurrent()方法绑定OpenGL ES线程环境
- shader的完整代码
- egl_video_shader.h
class EGLVideoShader { public: void Init(int width, int height); void Render(AVFrame *frame, std::function<void()> renderCallback); void Release(); private: GLint InitShader(const char *code, GLint type); private: //加入三维顶点数据 两个三角形组成正方形 float vers[12] = { 1.0f, -1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, }; //加入材质坐标数据 float txts[8] = { 1.0f, 0.0f, //右下 0.0f, 0.0f, 1.0f, 1.0f, 0.0, 1.0 }; //顶点着色器代码 const char *vertexSource = R"( attribute vec4 aPosition; //顶点坐标 attribute vec2 aTexCoord; //材质顶点坐标 varying vec2 vTexCoord; //输出的材质坐标 void main(){ vTexCoord = vec2(aTexCoord.x,1.0-aTexCoord.y); gl_Position = aPosition; } )"; //片元着色器代码 const char *fragmentSource = R"( precision mediump float; //精度 varying vec2 vTexCoord; //顶点着色器传递的坐标 uniform sampler2D yTexture; //输入的材质(不透明灰度,单像素) uniform sampler2D uTexture; uniform sampler2D vTexture; void main(){ vec3 yuv; vec3 rgb; yuv.r = texture2D(yTexture,vTexCoord).r; yuv.g = texture2D(uTexture,vTexCoord).r - 0.5; yuv.b = texture2D(vTexture,vTexCoord).r - 0.5; rgb = mat3(1.0, 1.0, 1.0, 0.0,-0.39465,2.03211, 1.13983,-0.58060,0.0)*yuv; //输出像素颜色 gl_FragColor = vec4(rgb,1.0); } )"; int width = 0; int height = 0; //创建opengl纹理 GLuint texts[3] = {0}; //材质内存空间 unsigned char *datas[3] = {nullptr, nullptr, nullptr}; GLint program; GLint vsh; GLint fsh; };
- egl_video_shader.cpp
GLint EGLVideoShader::InitShader(const char *code, GLint type) { //创建shader GLint sh = glCreateShader(type); if (sh == 0) { LOG_D("glCreateShader %d failed!", type); GLenum error = glGetError(); LOG_D("glCreateShader(%d) failed! Error: 0x%X", type, error); return 0; } //加载shader glShaderSource(sh, 1, //shader数量 &code, //shader代码 0); //代码长度 //编译shader glCompileShader(sh); //获取编译情况 GLint status; glGetShaderiv(sh, GL_COMPILE_STATUS, &status); if (status == 0) { LOG_D("glCompileShader failed!"); return 0; } LOG_D("glCompileShader success!"); return sh; } void EGLVideoShader::Init(int width, int height) { this->width = width; this->height = height; LOG_I("删除datas start"); if (datas[0] != nullptr) { // 检查是否为有效指针 delete[] datas[0]; // 释放Y分量数组 delete[] datas[1]; // 释放U分量数组 delete[] datas[2]; // 释放V分量数组 // 释放后重置为nullptr,避免重复释放 datas[0] = datas[1] = datas[2] = nullptr; } LOG_I("删除datas end"); ///分配材质内存空间 datas[0] = new unsigned char[width * height]; //Y datas[1] = new unsigned char[width * height / 4]; //U datas[2] = new unsigned char[width * height / 4]; //V if (texts[0]) { glDeleteTextures(3, texts); } LOG_I("开始初始化Shader"); //创建三个纹理 glGenTextures(3, texts); //顶点和片元shader初始化 //顶点shader初始化 vsh = InitShader(vertexSource, GL_VERTEX_SHADER); //片元yuv420 shader初始化 fsh = InitShader(fragmentSource, GL_FRAGMENT_SHADER); ///////////////////////////////////////////////////////////// //创建渲染程序 program = glCreateProgram(); if (program == 0) { LOG_D("glCreateProgram failed!"); return; } //渲染程序中加入着色器代码 glAttachShader(program, vsh); glAttachShader(program, fsh); //链接程序 glLinkProgram(program); GLint status = 0; glGetProgramiv(program, GL_LINK_STATUS, &status); if (status != GL_TRUE) { LOG_D("glLinkProgram failed!"); return; } glUseProgram(program); LOG_D("glLinkProgram success!"); GLuint apos = (GLuint) glGetAttribLocation(program, "aPosition"); glEnableVertexAttribArray(apos); //传递顶点 glVertexAttribPointer(apos, 3, GL_FLOAT, GL_FALSE, 12, vers); GLuint atex = (GLuint) glGetAttribLocation(program, "aTexCoord"); glEnableVertexAttribArray(atex); glVertexAttribPointer(atex, 2, GL_FLOAT, GL_FALSE, 8, txts); //材质纹理初始化 //设置纹理层 glUniform1i(glGetUniformLocation(program, "yTexture"), 0); //对于纹理第1层 glUniform1i(glGetUniformLocation(program, "uTexture"), 1); //对于纹理第2层 glUniform1i(glGetUniformLocation(program, "vTexture"), 2); //对于纹理第3层 //设置纹理属性 glBindTexture(GL_TEXTURE_2D, texts[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, //细节基本 0默认 GL_LUMINANCE,//gpu内部格式 亮度,灰度图 width, height, //拉升到全屏 0, //边框 GL_LUMINANCE,//数据的像素格式 亮度,灰度图 要与上面一致 GL_UNSIGNED_BYTE, //像素的数据类型 NULL //纹理的数据 ); //设置纹理属性 glBindTexture(GL_TEXTURE_2D, texts[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, //细节基本 0默认 GL_LUMINANCE,//gpu内部格式 亮度,灰度图 width / 2, height / 2, //拉升到全屏 0, //边框 GL_LUMINANCE,//数据的像素格式 亮度,灰度图 要与上面一致 GL_UNSIGNED_BYTE, //像素的数据类型 NULL //纹理的数据 ); //设置纹理属性 glBindTexture(GL_TEXTURE_2D, texts[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, //细节基本 0默认 GL_LUMINANCE,//gpu内部格式 亮度,灰度图 width / 2, height / 2, //拉升到全屏 0, //边框 GL_LUMINANCE,//数据的像素格式 亮度,灰度图 要与上面一致 GL_UNSIGNED_BYTE, //像素的数据类型 NULL //纹理的数据 ); LOG_I("Shader初始化成功"); } void EGLVideoShader::Render(AVFrame *frame, std::function<void()> renderCallback) { // LOG_E("渲染:width=%d,height=%d,frame->width=%d,frame->height=%d",width,height,frame->width,frame->height); if (!datas[0] || width * height == 0 || frame->width != this->width || frame->height != this->height) { LOG_E("空数据开始释放内存AVFrame"); av_frame_free(&frame); return; } // LOG_E("Start Render View"); if (width == frame->linesize[0]) //无需对齐 { LOG_E("无需对齐数据->start"); memcpy(datas[0], frame->data[0], width * height); memcpy(datas[1], frame->data[1], width * height / 4); memcpy(datas[2], frame->data[2], width * height / 4); LOG_E("无需对齐数据->end"); } else//行对齐问题 { // LOG_E("逐行copy数据->start"); for (int i = 0; i < height; i++) //Y memcpy(datas[0] + width * i, frame->data[0] + frame->linesize[0] * i, width); for (int i = 0; i < height / 2; i++) //U memcpy(datas[1] + width / 2 * i, frame->data[1] + frame->linesize[1] * i, width); for (int i = 0; i < height / 2; i++) //V memcpy(datas[2] + width / 2 * i, frame->data[2] + frame->linesize[2] * i, width); // LOG_E("Repaint->逐行copy数据->end"); } av_frame_free(&frame); // LOG_E("释放AVFrame"); //激活第1层纹理,绑定到创建的opengl纹理 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texts[0]); //替换纹理内容 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, datas[0]); //激活第2层纹理,绑定到创建的opengl纹理 glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, texts[1]); //替换纹理内容 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, datas[1]); //激活第2层纹理,绑定到创建的opengl纹理 glActiveTexture(GL_TEXTURE0 + 2); glBindTexture(GL_TEXTURE_2D, texts[2]); //替换纹理内容 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, datas[2]); //三维绘制 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); //窗口显示 renderCallback(); } void EGLVideoShader::Release() { width = 0; height = 0; //材质内存空间 if (datas[0] != nullptr) { // 检查是否为有效指针 delete[] datas[0]; // 释放Y分量数组 delete[] datas[1]; // 释放U分量数组 delete[] datas[2]; // 释放V分量数组 // 释放后重置为nullptr,避免重复释放 datas[0] = datas[1] = datas[2] = nullptr; } // 释放纹理 if (texts[0] != 0) { glDeleteTextures(3, texts); texts[0] = texts[1] = texts[2] = 0; } if (program != 0) { glUseProgram(0); // 分离着色器 if (vsh != 0) { glDetachShader(program, vsh); } if (fsh != 0) { glDetachShader(program, fsh); } glDeleteProgram(program); program = 0; } // 释放着色器 if (vsh != 0) { glDeleteShader(vsh); vsh = 0; } if (fsh != 0) { glDeleteShader(fsh); fsh = 0; } LOG_I("Release释放内存及显存"); }
- egl_video_shader.h
浙公网安备 33010602011771号