Android ExoPlayer使用GLSurfaceView播放视频

3,000 阅读12分钟

前言

之前的博客涉及的很多都是UI和质量相关的文章,我们基本涉及了很多方面,最近一篇是基于Jetpack Compose实现的ScrollConnection基准型布局,大家有兴趣可以看看。

当然,本篇我们继续做播放相关的文章,实际上在我之前也出了好多篇播放相关的文章,只是不在掘金平台,后续有机会我们转移过来。

关于GLSurfaceView

对于音视频的开发者而言,对于GLSurfaceView实际上既熟悉又陌生,主要原因是在Android体系中,我本身是单独绘制的且能和SurfaceFlinger直接通信,GLSurfaceView他和SurfaceView存在继承关系,但渲染方面区别很大,特别是具体用法上,GLSurfaceView并不能直接使用SurfaceHolder中的Surface,因为surface会被EglSurface引用,如果要实现渲染,需要单独创建纹理。

当然,相似的地方包括生命周期都是一致的,这也是意味着,只要Activity在后台,那么GLSurfaceView的绘制功能也会受到相应的影响。比如surface销毁。

但是,SurfaceView的Surface销毁并不意味着opengl纹理销毁,因此视频渲染仍然会继续,并且SurfaceView的Surface重新创建时,关联EglSurface之后画面继续。这和SurfaceView的调用逻辑完全不同,后者Surface的失效一般会引发MediaCodec解码异常。

opengl渲染有很多好处,利用GLSurfaceView不仅仅可以实现特效和水印,而且可以解决SurfaceView动画不支持的问题、以及feed流滑动引发画面漂移的问题。

本篇对于开发者而言,其实是入门篇,不过这里我们先需要简单理解下GLSurfaceView。

EGL环境

实际上,在Android系统中使用open gl es渲染并不意味着一定要使用GLSurfaceView,对于SurfaceView、TextureView同样也可以使用open gl,即便是普通View,我们依然可以拿到rgb buffer转换的Bitmap进行渲染。

但是,open gl es需要渲染环境的支持,目前Android平台是EGL,EGL可以看画布相关的环境,而open gl可以看做画笔,当然还有其他平台的另类环境也同样支持 open gl渲染。

Surface创建和渲染问题

GLSurfaceView和SurfaceView有很多不同的地方,最大的地方无疑是Surface的创建。在Android系统中Surface是具有双buffer的绘制通道。创建Surface的方法也很多,主要分为下面几个

  • SurfaceView:内部创建
  • TextureView: 使用surfaceTexture创建
  • MediaCodec: 提供Surface,实现录制
  • ImageReader: YUV数据读取,但要注意的是,部分设备上需要设置MediaCodec的色彩空间COLOR_FormatYUV420Flexible,否则拿不到数据。
  • open gl es: 生成纹理创建Surface

最简单的录屏

Surface我们之前在做《黑客代码雨》和《烟花效果中》都使用过,其特点还有就是支持异步绘制。当然,看到一些人想做录屏的效果,很多人都去想着使用MediaProjection,实际上录屏很简单,只需要我们获取到DecorView,然后使用DecorView#draw方法UI到Bitmap上.

private void captureActivityWindow(Activity activity,String fileName) {
    try {
      
        View decorView = activity.getWindow().getDecorView();
        int width = decorView.getWidth();
        int height = decorView.getHeight();
   
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); //
        Canvas canvas = new Canvas(bitmap);
        decorView.draw(canvas);
        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 480, 270, true);
        File file = new File(MediaType.LOG.getPath(), fileName);
        if (file.exists()) {
           file.delete();
        }
        saveBitmap(scaledBitmap, file);
        scaledBitmap.recycle();
        bitmap.recycle();
       
    }catch (Throwable e){
        e.printStackTrace();
    }
}

然后绘制到MediaCodec#createInputSurface()上即可

Canvas canvas = surface.lockCanvas(null);
//调用lockHardwareCanvas会后台再回来会出现画面卡主,这里我们使用lockCanvas比较保险
canvas.drawBitmap(bitmap,0,0,null);
surface.unlockCanvasAndPost(canvas);
bitmap.recycle();

画面控制在24帧即可,将上面的代码定时1000/24ms执行即可。

Surface使用异常

Surface 最容易触发的两大错误分别是失效绘制和重复引用

错误码如下:

  • 失效后渲染: -19
  • 被多个播放器同时引用: -22

如下是Surface失效导致的问题,这种情况会导致播放器渲染错误

09-09 10:52:21.324 23846 11142 D SurfaceUtils: disconnecting from surface 0xab257008, reason disconnectFromSurface
09-09 10:52:21.324  1385  1661 I TrafficMonitor: update:rxPkts:78,txPkts:53,rxBytes:31585,txBytes:11069
09-09 10:52:21.324 23846 11142 E SurfaceUtils: Failed to disconnect from surface 0xab257008, err -19
09-09 10:52:21.324 23846 11142 W MediaCodec: nativeWindowDisconnect returned an error: No such device (-19)

遗憾的是以上错误码只能从系统日志中看到,所以必要时需要拦截native层日志才能获取到错误码。

SurfaceView 遮挡的问题

SurfaceView遮挡问题是开发者的重灾区,谷歌官方似乎也没有很好的办法,逼急了他会建议你使用TextureView,但是对于太卡顿的设备,这种无异于饮鸩止渴。

SurfaceView遮挡SurfaceView解决方法

如果UI上存在多个SurfaceView,SurfaceView与SurfaceView如果存在遮盖,这种方式相对而言还是比较好处理的,使用如下方式即可

setZOrderMediaOverlay(true);
##setZOrderOnTop(true); //一般不建议

SurfaceView 遮挡普通View

但是如果SurfaceView和普通View出现遮盖问题,特别是明明普通的View在SurfaceView上面,却展示不出来,这个时候使用上面的方式是无效的,那么怎么处理呢?

SurfaceView遮挡上方View

其实方法很简单,就是在surfaceCreated被调用之前,设置SurfaceView的背景,就能解决遮住问题

this.setBackgroundColor(Color.TRANSPARENT);  //修复遮住上层View的问题

SurfaceView遮挡下方View

但是,上面的办法并不总是有用,这种只能解决在SurfaceView布局上面的普通View被遮挡的问题,一些实际开发中往往存在很奇怪的现象,明明SurfaceView大小为200x200,但是会导致SurfaceView下方普通View,甚至不重叠的普通View也无法展示。

这种情况是最复杂的,也是更多人会遇到的,甚至有些人会束手无策,这种场景处理不当,导致兴致勃勃的开发者SurfaceView优化性能时止步不前。

那么,怎么解决这种问题呢?

其实,方法是有的,但是需要按原则优化:

  • 保持SurfaceView在View树种层级更加靠近底部,也就是说SurfaceView越接近根布局越好
  • 保持SurfaceView在同一个布局中的顺序索引越小越好,也就是尽可能让SurfaceView优先被addView到布局中

其实就一句话:尽量让普通View在SurfaceView上方,SurfaceView尽量在下方

SurfaceView 黑屏问题

实际上,这部分原因比较复杂,但是有一种可行的方案

解决方法:

  • this.setBackgroundColor(Color.TRANSPARENT);
  • surfaceCreated 绘制Surface,将其背景使用Canvas清空颜色

注意:不保证适用所有场景

关于ExoPlayer

ExoPlayer 作为Android官方力推的播放器,其兼容性和可扩展性优势很大。MediaPlayer作为系统播放器,其局限性在之前的文章中我们说过,MediaPlayer作为C/S架构,我们仅仅能处理Client层的问题,至于Server属于系统层代码,Android官方是不会允许你修改Server的,因而MediaPlayer无法扩展,同时兼容性稍差。不过好处是,由于核心逻辑运行在系统进程,可以帮助应用层app减少内存占用,相当于薅羊毛了。

下面是简单的用法

DefaultRenderersFactory defaultRenderersFactory = new DefaultRenderersFactory(getApplicationContext());
defaultRenderersFactory.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
ExoPlayer player = new ExoPlayer.Builder(getApplicationContext()).setRenderersFactory(defaultRenderersFactory).build();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.setMediaSource(mediaSource);
player.prepare();
player.setVideoSurface(surface);

用法

GLSurfaceView的Renderer

GLSurfaceView的使用是存在一些难度的,相比SurfaceView,GLSurfaceView使用Renderer

android.opengl.GLSurfaceView#setRenderer

下面我们以官方demo为例,做一些说明。

private final class VideoRenderer implements GLSurfaceView.Renderer, VideoFrameMetadataListener {

  private final VideoProcessor videoProcessor;
  private final AtomicBoolean frameAvailable;
  private final TimedValueQueue<Long> sampleTimestampQueue;
  private final float[] transformMatrix;

  private int texture;
  @Nullable private SurfaceTexture surfaceTexture;

  private boolean initialized;
  private int width;
  private int height;
  private long frameTimestampUs;

  public VideoRenderer(VideoProcessor videoProcessor) {
    this.videoProcessor = videoProcessor;
    frameAvailable = new AtomicBoolean();
    sampleTimestampQueue = new TimedValueQueue<>();
    width = -1;
    height = -1;
    frameTimestampUs = C.TIME_UNSET;
    transformMatrix = new float[16]; //转换矩阵 4x4
  }

  @Override
  public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) {
    try {
      texture = GlUtil.createExternalTexture();
    } catch (GlUtil.GlException e) {
      Log.e(TAG, "Failed to create an external texture", e);
    }
    surfaceTexture = new SurfaceTexture(texture);
    surfaceTexture.setOnFrameAvailableListener(
        surfaceTexture -> {
          frameAvailable.set(true);
          requestRender();
        });
    onSurfaceTextureAvailable(surfaceTexture);
  }

  @Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0, 0, width, height);
    this.width = width;
    this.height = height;
  }

//下面方法负责绘制每一帧画面
  @Override
  public void onDrawFrame(GL10 gl) {
    if (videoProcessor == null) {
      return;
    }

    if (!initialized) {
      videoProcessor.initialize();
      initialized = true;
    }

    if (width != -1 && height != -1) {
      videoProcessor.setSurfaceSize(width, height);
      width = -1;
      height = -1;
    }

    if (frameAvailable.compareAndSet(true, false)) {
      SurfaceTexture surfaceTexture = Assertions.checkNotNull(this.surfaceTexture);
      surfaceTexture.updateTexImage();
      long lastFrameTimestampNs = surfaceTexture.getTimestamp();
      @Nullable Long frameTimestampUs = sampleTimestampQueue.poll(lastFrameTimestampNs);
      if (frameTimestampUs != null) {
        this.frameTimestampUs = frameTimestampUs;
      }
      surfaceTexture.getTransformMatrix(transformMatrix);
    }

    videoProcessor.draw(texture, frameTimestampUs, transformMatrix);
  }

  @Override
  public void onVideoFrameAboutToBeRendered(
      long presentationTimeUs,
      long releaseTimeNs,
      Format format,
      @Nullable MediaFormat mediaFormat) {
    sampleTimestampQueue.add(releaseTimeNs, presentationTimeUs);
  }
}

在上面的代码中,我们要注意的是SurfaceTexture的创建

texture = GlUtil.createExternalTexture();

其最终代码是 这里创建了纹理,用于创建SurfaceTexture,但是我们绑定OES,为后续Shader脚本中的Sampler2D服务。

private static int generateTexture() throws GlException {
  checkGlException(
      !Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");

  int[] texId = new int[1];
  GLES20.glGenTextures(/* n= */ 1, texId, /* offset= */ 0);
  checkGlError();
  bindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId[0]);
  return texId[0];
}

初始化EGL

下面方法用于初始化EGL环境,设置版本号为2,其次设置Renderer,但是ExoPayer的demo还做了一些环境修改,实际上使用Android平台默认的也行,不过下面的修改有一些问题需要注意一下,其中EGL_PROTECTED_CONTENT_EXT会导致我们无法使用glReadPixeles读取数据。

public VideoProcessingGLSurfaceView(
    Context context, boolean requireSecureContext, VideoProcessor videoProcessor) {
  super(context);
  renderer = new VideoRenderer(videoProcessor);
  mainHandler = new Handler();
  setEGLContextClientVersion(2);
  setEGLConfigChooser(
      /* redSize= */ 8,
      /* greenSize= */ 8,
      /* blueSize= */ 8,
      /* alphaSize= */ 8,
      /* depthSize= */ 0,
      /* stencilSize= */ 0);
  setEGLContextFactory(
      new EGLContextFactory() {
        @Override
        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
          int[] glAttributes;
          if (requireSecureContext) {
            glAttributes =
                new int[] {
                  EGL14.EGL_CONTEXT_CLIENT_VERSION,
                  2,
                  EGL_PROTECTED_CONTENT_EXT,
                  EGL14.EGL_TRUE,
                  EGL14.EGL_NONE
                };
          } else {
            glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
          }
          return egl.eglCreateContext(
              display, eglConfig, /* share_context= */ EGL10.EGL_NO_CONTEXT, glAttributes);
        }

        @Override
        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
          egl.eglDestroyContext(display, context);
        }
      });
  setEGLWindowSurfaceFactory(
      new EGLWindowSurfaceFactory() {
        @Override
        public EGLSurface createWindowSurface(
            EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) {
          int[] attribsList =
              requireSecureContext
                  ? new int[] {EGL_PROTECTED_CONTENT_EXT, EGL14.EGL_TRUE, EGL10.EGL_NONE}
                  : new int[] {EGL10.EGL_NONE};
          return egl.eglCreateWindowSurface(display, config, nativeWindow, attribsList);
        }

        @Override
        public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
          egl.eglDestroySurface(display, surface);
        }
      });
  setRenderer(renderer);
  setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); //手动刷新调用onDrawFrame而不是自动刷新
}

上面的代码中,我们尤其要注意EGLSurface的创建,没有EGLSurface降到纹理无法渲染,我们可以将EGLSurface看做egl环境的画布。这里相当于将SurfaceView的Surface和EGL环境绑定。

public EGLSurface createWindowSurface(
    EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) {
  int[] attribsList =
      requireSecureContext
          ? new int[] {EGL_PROTECTED_CONTENT_EXT, EGL14.EGL_TRUE, EGL10.EGL_NONE}
          : new int[] {EGL10.EGL_NONE};
  return egl.eglCreateWindowSurface(display, config, nativeWindow, attribsList);
}

至于eglCreateWindowSurface,其实现主要是为了SurfaceView,具体代码如下

public static EGLSurface eglCreateWindowSurface(EGLDisplay dpy,
    EGLConfig config,
    Object win,
    int[] attrib_list,
    int offset
){
    Surface sur = null;
    if (win instanceof SurfaceView) {
        SurfaceView surfaceView = (SurfaceView)win;
        sur = surfaceView.getHolder().getSurface();
    } else if (win instanceof SurfaceHolder) {
        SurfaceHolder holder = (SurfaceHolder)win;
        sur = holder.getSurface();
    } else if (win instanceof Surface) {
        sur = (Surface) win;
    }

    EGLSurface surface;
    if (sur != null) {
        surface = _eglCreateWindowSurface(dpy, config, sur, attrib_list, offset);
    } else if (win instanceof SurfaceTexture) {
        surface = _eglCreateWindowSurfaceTexture(dpy, config,
                win, attrib_list, offset);
    } else {
        throw new java.lang.UnsupportedOperationException(
            "eglCreateWindowSurface() can only be called with an instance of " +
            "Surface, SurfaceView, SurfaceTexture or SurfaceHolder at the moment, " +
            "this will be fixed later.");
    }

    return surface;
}

绘制逻辑

官方demo提供的是一个水印效果,代码量稍微有些大,因此我代码里注释一下。我们先看Shader,在open gl中环境中,从渲染到着色有多个步骤,这个在我们之前的文章中也说过,这里就不在赘述了。

顶点Shader

attribute vec4 aFramePosition;
attribute vec4 aTexCoords;
uniform mat4 uTexTransform; //矩阵变换
varying vec2 vTexCoords;  //输出到片断Shader中
void main() {
 gl_Position = aFramePosition;  //定点坐标
 vTexCoords = (uTexTransform * aTexCoords).xy;
}

片段Shader

#extension GL_OES_EGL_image_external : require
precision mediump float;
// External texture containing video decoder output.
uniform samplerExternalOES uTexSampler0;
// Texture containing the overlap bitmap.
uniform sampler2D uTexSampler1;
// Horizontal scaling factor for the overlap bitmap.
uniform float uScaleX;
// Vertical scaling factory for the overlap bitmap.
uniform float uScaleY;
varying vec2 vTexCoords;
void main() {
  vec4 videoColor = texture2D(uTexSampler0, vTexCoords); //视频帧坐标
  vec4 overlayColor = texture2D(uTexSampler1,
                                vec2(vTexCoords.x * uScaleX,
                                     vTexCoords.y * uScaleY)); // bitmap 坐标
  // Blend the video decoder output and the overlay bitmap.
  gl_FragColor = videoColor * (1.0 - overlayColor.a)
      + overlayColor * overlayColor.a;  //最终纹理结果
}

注意,视频视同的纹理采样变量类型是samplerExternalOES,而图片使用sampler2D

上面是主要了两个Shader,主要在GPU中执行和计算,第一个负责计算顶点坐标,第二个用于顶点坐标着色。

/* package */ final class BitmapOverlayVideoProcessor
    implements VideoProcessingGLSurfaceView.VideoProcessor {

  private static final String TAG = "BitmapOverlayVP";
  private static final int OVERLAY_WIDTH = 512;  // 水印长度
  private static final int OVERLAY_HEIGHT = 256; //水印高度

  private final Context context;
  private final Paint paint;
  private final int[] textures;
  private final Bitmap overlayBitmap; // 其他图片
  private final Bitmap logoBitmap; //logo
  private final Canvas overlayCanvas;

  private @MonotonicNonNull GlProgram program;

  private float bitmapScaleX;
  private float bitmapScaleY;

  public BitmapOverlayVideoProcessor(Context context) {
    this.context = context.getApplicationContext();
    paint = new Paint();
    paint.setTextSize(64);
    paint.setAntiAlias(true);
    paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
    textures = new int[1];
    overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
    overlayCanvas = new Canvas(overlayBitmap);
    try {
      logoBitmap =
          ((BitmapDrawable)
                  context.getPackageManager().getApplicationIcon(context.getPackageName()))
              .getBitmap();
    } catch (PackageManager.NameNotFoundException e) {
      throw new IllegalStateException(e);
    }
  }

  @Override
  public void initialize() {
    try {
      program =
          new GlProgram(
              context,
              /* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl",
              /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
    } catch (IOException e) {
      throw new IllegalStateException(e);
    } catch (GlUtil.GlException e) {
      Log.e(TAG, "Failed to initialize the shader program", e);
      return;
    }
    program.setBufferAttribute(
        "aFramePosition",
        GlUtil.getNormalizedCoordinateBounds(),
        GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
    program.setBufferAttribute(
        "aTexCoords",
        GlUtil.getTextureCoordinateBounds(),
        GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
    GLES20.glGenTextures(1, textures, 0);  //创建图片纹理
    
    /**
    *下面载入overlayBitmap,并且载入双线性过滤,要注意的是,图片和视频的线性过滤是不一样的
    */
    GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
    GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
    GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
    GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
    GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
  }

  @Override
  public void setSurfaceSize(int width, int height) {
    bitmapScaleX = (float) width / OVERLAY_WIDTH;
    bitmapScaleY = (float) height / OVERLAY_HEIGHT;
  }

  @Override
  public void draw(int frameTexture, long frameTimestampUs, float[] transformMatrix) {
  //绘制Bitmap
    // Draw to the canvas and store it in a texture.
    String text = String.format(Locale.US, "%.02f", frameTimestampUs / (float) C.MICROS_PER_SECOND);
    overlayBitmap.eraseColor(Color.TRANSPARENT);
    overlayCanvas.drawBitmap(logoBitmap, /* left= */ 32, /* top= */ 32, paint);
    overlayCanvas.drawText(text, /* x= */ 200, /* y= */ 130, paint);
    GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);  //绑定Bitmap纹理
    GLUtils.texSubImage2D(
        GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap);
    try {
      GlUtil.checkGlError();
    } catch (GlUtil.GlException e) {
      Log.e(TAG, "Failed to populate the texture", e);
    }

    // Run the shader program.
    GlProgram program = checkNotNull(this.program);
    //绑定图片纹理
    program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0);
    //绑定视频纹理
    program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1);
    program.setFloatUniform("uScaleX", bitmapScaleX);
    program.setFloatUniform("uScaleY", bitmapScaleY);
    program.setFloatsUniform("uTexTransform", transformMatrix);
    try {
      program.bindAttributesAndUniforms();
    } catch (GlUtil.GlException e) {
      Log.e(TAG, "Failed to update the shader program", e);
    }
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
    try {
      GlUtil.checkGlError();
    } catch (GlUtil.GlException e) {
      Log.e(TAG, "Failed to draw a frame", e);
    }
  }

  @Override
  public void release() {
    if (program != null) {
      try {
        program.delete();
      } catch (GlUtil.GlException e) {
        Log.e(TAG, "Failed to delete the shader program", e);
      }
    }
  }
}

上面的代码中,我们要注意图片的纹理和视频的纹理存在一些差别,主要如下

  • 过滤器: 视频使用GL_TEXTURE_EXTERNAL_OES,而图片使用GL_TEXTURE_2D
  • 纹理载入:图片需要使用texSubImage2D,而视频不需要
  • 缩放:上面的逻辑会对图片纹理进行缩放,便于计算gl_FragColor

以上就是完整的调用流程

SurfaceTexture 宽高为0的问题

上面的代码实现播放视频完全是没有问题的,但是如果是下面则存在问题

GLSurface->surfaceTexure->EGLSurface->EglSwappers

然而,当我们使用eglCreatePbufferSurface 离屏渲染,将GLSurfaceView的SurfaceTexture使用createWindowSurface绑定EGLSurface时,就会发现EGLSurface大小为0,主要原因是SurfaceTexture大小是不对的,但是使用SurfaceView和TextureView的完全正常播放?

原因是本篇我们的SurfaceTexture没有设置大小,这个可以使用EGL14.queryEglSurface去查询宽高,结果是0。

解决方法:

设置大小即可解决此问题

this.surfaceTexture.setDefaultBufferSize(width,height);

总结

实际上本篇主要还是GLSurfaceView的用法,对于这部分而言,我们能看到open gl其本身不是面向对象的,因此一些处理流程的可视化和关联性很难去理解,但是好处是open gl是和线程、EGL Context一一对应的,因此我们从这方面去理解要简单的多。

好了,本篇就到这里,希望对你有所帮助。