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

来源:互联网 发布:内存整型数据是啥 编辑:程序博客网 时间:2024/05/19 18:17

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;

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

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

保证了两个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"

这一部分的程序主要是在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)

这个使得我们通知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

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 0
原创粉丝点击