5. MediaPlayer + SurfaceTexture

    播放组件上层使用MediaPlayer来处理,在成功创建并设置好setDataSource后,需要创建GL_TEXTURE_EXTERNAL_OES格式的纹理ID来与MediaPlayer生成联系。

phpLGJcKW.1449653590.png

    在这里我们需要使用SurfaceTexture的理由是,它能代替SurfaceHolder,使得当我们指定图像流的输出目标为照相机预览或视频解码时,我们在每一帧中得到的所有数据不需要直接用于显示在设备上,而是可以选择先输出到SurfaceTexture上,在上屏之前可能做一些自定义扩展。当调用updateTexImage()时,用来创建SurfaceTexture的纹理对象内容被更新为包含图像流中最近的图片。

    SurfaceTexture对象可以在任何线程里创建。但updateTexImage()只能在包含纹理对象的OpenGL ES上下文所在的线程里创建。可以得到帧信息的回调可以在任何线程被调用。这一点要注意,上下文如果不一致,视频无法上屏。
    这里还有个要求就是在创建纹理的时候,需要使用使用GL_TEXTURE_EXTERNAL_OES作为纹理目标,其是OpenGL ES扩展GL_OES_EGL_image_external定义的。这种纹理目标会对纹理的使用方式造成一些限制。每次纹理绑定的时候,都要绑定到GL_TEXTURE_EXTERNAL_OES,而不是GL_TEXTURE_2D。而且,任何需要从纹理中采样的OpenGL ES 2.0 shader都需要声明其对此扩展的使用,例如,使用指令”#extension GL_OES_EGL_image_external:require”。这些shader也必须使用samplerExternalOES采样方式来访问纹理。这部分在后面采样器中有说明。
 
     几个重要的技术点如下:

 

  • Shader采样 YUV->RGB

    这里片元着色器的使用如下:

phpxEEbRZ.1449653616.png

    该Shader是针对GL_TEXTURE_EXTERNAL_OES的一种扩展,完成YUV到RGB的转换,采样出来的数据需要绘制到Unity纹理上,这里面不能直接操作,需要借助FBO和EGLImage来操作。该片元着色器的写法在使用SurfaceTexture里面已经有提及。

  • EGLImage

    EGLImage代表一种由EGL客户API(如OpenGL,OpenVG)创建的共享资源类型。它的本意是共享2D图像数据,而在这里使用它的目的在于经过EGLImage创建的2D纹理,能够bind到任意的载体GameObject上面。

    如何创建EGLImage,创建的标准接口如下
    EGLImageKHR eglCreateImageKHR(
                            EGLDisplay dpy,
                            EGLContext ctx,
                            EGLenum target,
                            EGLClientBuffer buffer,
                            const EGLint *attrib_list);
    target决定了创建EGLImage的方式,例如在Android系统中专门定义了一个称为EGL_NATIVE_BUFFER_ANDROID的Target,支持通过ANativeWindowBuffer创建EGLImage对象,而Buffer则对应创建EGLImage对象时的使用数据来源。
    1) 首先需要一个ANativeWindowBuffer。
    该buffer可以通过ANativeWindow的接口dequeueBuffer来获取。

php1WYhDQ.1449653644.png

    这个对象的api接口较多,它对buffer的管理类似于如下

phpTjopU4.1449653658.png

    这部分操作可以参考下面这篇文章

    http://tangzm.com/blog/?p=167
    在获取buffer之前要创建一个ANativeWindow对象。

php0HVAE5.1449653679.png

    2) 通过该ANativeWindowBuffer来创建EGLImage

phplatIS4.1449653742.png

    3) 成功创建了EGLImage后,可能通过它来创建一个2D纹理

phpMAOaVK.1449653871.png

    这个2D纹理在后面创建FBO的时候会用到。

  • FBO - Frame Buffer Object

    FBO即帧缓存对象,它是OpenGL管线的最终渲染目的地。在OpenGL渲染管线中,几何数据和纹理在FBO内部经过多次转化和多次测试,最后以二维像素的形式显示在屏幕上。它是一些二维数组和OpenG所使用的存储区的集合:颜色缓存、深度缓存、模板缓存和累计缓存。默认情况下,OpenGL将帧缓冲区作为渲染最终目的地。此帧缓冲区完全由window系统生成和管理。这个默认的帧缓存被称作“window系统生成”(window-system-provided)的帧缓冲区。

    有两种类型的“帧缓存关联图像”:纹理图像(texture images)和渲染缓存图像(renderbuffer images)。如果纹理对象的图像数据关联到帧缓存,OpenGL执行的是“渲染到纹理”(render to texture)操作。如果渲染缓存的图像数据关联到帧缓存,OpenGL执行的是离线渲染(offscreen rendering)。
    具体的操作如下:

phpjCi848.1449653895.png

    在这里使用了通过上一步EGLImage生成的2D纹理mTex,这样的话后续就可以通过操作FBO对象来获取MediaPlayer中SurfaceTexture中的每一帧数据。

关于FBO的详细接口说明,可见下面链接
    http://blog.csdn.net/dreamcs/article/details/7691690
  • SurfaceTexture.updateTexImage

    当MediaPlayer中当新的帧流可用时,调用updateTexImage从图像流中提取最近一帧到纹理图像中,此时由于同处一个上下文中,首先需要执行一次FBO绑定操作,这是由于GL_TEXTURE_EXTERNAL_OES的特性决定的,实际上这个操作是为了下帧准备的。

phpQq67S2.1449653921.png

    然后将shader采样出来的数据帧跟Unity纹理ID绑定后就可上屏。

phpgZIoQF.1449653939.png

    PS: 这里没有截很代码的详细逻辑,只是原理,可能有理解不到位的地方,欢迎指证。