基于Surface的视频编解码与OpenGL ES渲染

http://blog.csdn.net/gh_home/article/details/52399959


1. 概述

这篇文章所做的事情是这样的: 
1. 从一个.mp4文件中解码视频流到surface上 
2. 利用OpenGL ES渲染改变视频流中每一帧的内容 
3. 将改变后的视频流重新编码输出到一个新的.mp4文件

所有代码可在此处下载:https://github.com/GH-HOME/DecodeEncodeMP4

2. 数据流

图像的数据流按照以下方式传递。 
这里写图片描述

3. 工作流

1. 初始化MediaEncoder(视频编码)、MediaDecoder(视频解码)、MediaMux(生成MP4文件合成音频)、MediaExtractor(分割视频与音频)。在这一步中,encoder和decoder需要分别绑定一个surface。

核心代码如下: 
1)初始化编码器与MediaMux

     MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
     format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
             MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
     format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
     format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
     format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
     encoder = null;
     try {
         encoder = MediaCodec.createEncoderByType(MIME_TYPE);
         encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
         encodesurface=encoder.createInputSurface();
         encoder.start();
         mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
     }catch (IOException ioe) {
         throw new RuntimeException("failed init encoder", ioe);
     }
     mTrackIndex = -1;
     mMuxerStarted = false;

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2)初始化解码器与MediaExtractor

try {
     File inputFile = new File(FILES_DIR, INPUT_FILE);   // must be an absolute path
     if (!inputFile.canRead()) {
          throw new FileNotFoundException("Unable to read " + inputFile);
      }
      extractor = new MediaExtractor();
      extractor.setDataSource(inputFile.toString());
      DecodetrackIndex = selectTrack(extractor);
      if (DecodetrackIndex < 0) {
          throw new RuntimeException("No video track found in " + inputFile);
      }
      extractor.selectTrack(DecodetrackIndex);

      MediaFormat format = extractor.getTrackFormat(DecodetrackIndex);
      if (VERBOSE) {
          Log.d(TAG, "Video size is " + format.getInteger(MediaFormat.KEY_WIDTH) + "x" +
                  format.getInteger(MediaFormat.KEY_HEIGHT));
      }

      outputSurface = new CodecOutputSurface(saveWidth, saveHeight,encodersurface);
      String mime = format.getString(MediaFormat.KEY_MIME);
      decoder = MediaCodec.createDecoderByType(mime);
      decoder.configure(format, outputSurface.getSurface(), null, 0);
      decoder.start();
  }catch (IOException e)
  {
      e.printStackTrace();
  }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
2. 初始化EGL,配置OpenGL的环境

这一步的相关概念可以参照 http://www.cnitblog.com/zouzheng/archive/2011/05/30/74326.html 
主要是配置以下几个内容

数据类型 取值
EGLDisplay (系统显示 ID 或句柄)
EGLConfig (Surface 的 EGL 配置)
EGLSurface (系统窗口或 frame buffer 句柄)
EGLContext (OpenGL ES 图形上下文)

在这里我们需要建立两个EGLContext ,一个用于控制接收从mp4文件中传过来的数据,一个用于控制将EGLSurface上的数据经过OPENGL ES渲染传递到encoder中的surface。在初始化第二个EGLContext 的时候需要将其与第一个EGLContext 绑定,这样两者可以共享一个Texture ID(也就是实际的图像数据)。

这部分代码较多:主要是CodecOutputSurface类中的eglsetup函数 
关键代码是这一句

mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
                attrib_list, 0);

        mEGLContextEncoder = EGL14.eglCreateContext(mEGLDisplay, configEncoder, mEGLContext,
                attrib_list, 0);

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

保证了两个EGLContext共享Texture id

3. OPENGL渲染的准备工作

这一步主要是分以下步骤: 
1. 写shader language的程序,包含VERTEX_SHADER和FRAGMENT_SHADER 
2. 编译链接以及加载上述程序 
3. 获取VERTEX_SHADER以及FRAGMENT_SHADER中的变量的句柄,创建Texture ID

在参考的Big Flake示例代码中发现在shader language中可以直接去渲染YUV420编码的数据,需要加以下标志声明

"#extension GL_OES_EGL_image_external : require\n"
 
 
  • 1
  • 1

这一部分的程序主要是在CodecOutputSurface类中的setup函数以及STextureRender类中的一些成员函数

4. 绘图程序的运行过程

在初始化编解码器后,将解码器对应的surface和一个SurfaceTexture绑定起来,同时SurfaceTexture的另外一边与OPRNGL ES中初始化建立的一个Texture ID绑定。这样就建立了一条由解码的mp4数据到OPENGL ES的Texture的数据流。 其中SurfaceTexture充当中介,在上述工作准备好后,开启SurfaceTexture内容侦听,即回调函数onFrameAvailable。 一旦SurfaceTexture内容发生变化(有新的编码数据流流入),系统会自动调用onFrameAvailable表明SurfaceTexture中有可用数据,之后我们调用SurfaceTexture的成员函数updateTexImage将当前的图像流传递到OPENGL ES中的texture。

在掉用GLES20绘图函数之前需要先调用

EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)
 
 
  • 1
  • 1

这个使得我们通知GPU以及OPENGL ES在执行绘图指令的时候,是在当前mEGLContext这个上下文绘制在mEGLSurface上的。所以我们在最后绘图的时候需要makecurrent到与Encoder绑定的surface对应的那个EGLSurface上:之前我们需要这样绑定这两个量

EGLSurface mEGLSurfaceEncoder = EGL14.eglCreateWindowSurface(mEGLDisplay, configEncoder, surface,
                surfaceAttribs2, 0);   //creates an EGL window surface and returns its handle
 
 
  • 1
  • 2
  • 1
  • 2

4. 总结

对于OpenGL ES与编解码的结合主要可以参考以下两个网站 
http://bigflake.com/mediacodec/ 
https://github.com/google/grafika

个人觉得grafika的模块化写的更好一点。总体来说,这个流程就是Encoder和Decoder把数据流弄到surface上,然后与OpenGL中的Texture id绑定,之后调用OPENGL ES的绘图指令渲染的过程,其中EGL就是给OPENGL ES方便移植做中间层的,它在程序中的作用就是为OPENGL ES做了初始化的工作,同时它也与mediacodec留有交互接口,因此说它是一个中间层


  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值