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);

       

二、代码示例

  代码部分介绍:

  1. 自定义SurfaceView关键代码
    1. 继承SurfaceView并重写SurfaceHolder.Callback
      class YEGLView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs), Runnable,
          SurfaceHolder.Callback 

       

    2. 重写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()
          }

       

  2. JNI部分关键代码
    const char *m_url = env->GetStringUTFChars(url, 0);
    yEglSampleGlesWindow.Init(g_jvm, surface);
    yEglSampleGlesWindow.Start(m_url);
    env->ReleaseStringUTFChars(url, m_url);//销毁url

     

  3. EGL工具类关键代码
    1. 初始化
      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;
          }
      
      }

       

    2. EGL绑定OpenGL ES线程
      void YEGLContextManager::MakeCurrent() {
          if (EGL_TRUE != eglMakeCurrent(display, winsurface, winsurface, context)) {
              LOG_D("eglMakeCurrent failed!");
              return;
          }
      }

       

    3. 显示窗口
      void YEGLContextManager::ShowWindow() {
          //窗口显示
          eglSwapBuffers(display, winsurface);
      }

       

  4. 渲染类的关键代码
    1. 在渲染线程中调用EGL的MakeCurrent()方法绑定OpenGL ES线程环境
      yeglContextManager.MakeCurrent();

       

    2. 初始化Shader
      eglVideoShader.Init(width, height);

       

    3. 循环渲染
          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();
              }
      
          }

       

  5. shader的完整代码
    1. 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;
      
      };

       

    2. 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释放内存及显存");
      }

       

  

posted on 2025-07-15 14:17  飘杨......  阅读(58)  评论(0)    收藏  举报