Opengl和MediaCodec的常见使用

一:综述

在Android中使用MediaCodec进行视频解码,将解码完成之后的视频解码到opengl的环境中,使用Opengl进行绘制,增加特效,多层显示控制等常见用法。

在一些Camera的应用中,进行滤镜或者对相机数据进行处理之后需要将视频数据录制出来,因为采用了Opengl进行滤镜显示,数据其实都已经送到了opengl环境中了,所以想要录制数据,需要将opengl中的纹理送到MediaCodec才能进行录制,所以需要配置Opengl和MediaCodec的联系,实现录制

二:MediaCodec解码视频和使用Opengl渲染

在Android中使用MediaCodec进行视频解码,解码输出到Opengl的环境中进行渲染,需要向MediaCodec 配置一个Surface,从而数据渲染到Surface上时,进一步通知渲染模块进行渲染。关键步骤在如何把解码和渲染中间联系起来,简单步骤如下:

1:opengl线程中创建OES纹理----->2:OES纹理作为参数,创建SurfaceTexture和Surface,并为Surface创建Listener ---------->3:将Surface设置给MeidaoCodec,根据业务流程启动解码----->4:解码完成,控制是否将数据渲染到surface上 ---->5:opengl 环境下的进行渲染

此处不具体展开具体的解码过程中的一些注意问题,如音视频同步,解封装mp4文件,创建opengl的program,以及shader脚本等前后问题,着重只介绍创建解码数据到opengl环境中渲染的关键几个步骤,

根据上面的流程我们把具体代码贴出来展示如下:

arduino 复制代码
    private String videoPath;
    private MediaCodecController mediaCodecController;//MediaoCodec的管理对象实例
    private PlayVideo playVideoFilter;//创建的Opengl实例
//1:创建OES纹理,此处创建必须在opengl的thread中,在非opengl的thread中创建不会成功
//2:
@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Log.i(TAG,"onSurfaceCreated");
​
        //先设置用于显示的纹理iD,这个是当前gl环境下产生的 纹理
        int oesTexture = createOESTexture();
        Log.i(TAG,"onSurfaceCreated oesTexture");
        mediaCodecController.setOesTexture(oesTexture);
        //设置要播放视频地址
        mediaCodecController.setCodecParams(videoPath);
        //创建oepngl的渲染程序
        playVideoFilter = new PlayVideoFilter(mContext);
        playVideoFilter.init();
    }

创建纹理成功之后,将纹理设置给到了mediaCodecController对象,mediaCodecController对象管理MediaCodec的实例,在收到纹理Id之后进行创建Surface

typescript 复制代码
  private int mOesTextureId = -1;
  private SurfaceTexture mVideoSurfaceTexture ;
  private Surface mDecodeSurface = null;
//2:mediaCodecController对象使用opengl创建的纹理作为入参创建Surface,此处在任何线程均可执行
private void createSurface(){
    mVideoSurfaceTexture = new SurfaceTexture(mOesTextureId);
    mDecodeSurface= new Surface(mVideoSurfaceTexture);
    //指定mHandler为解码完成数据时的回调线程
    mVideoSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener(){
        @Override
        public void onFrameAvailable(SurfaceTexture surfaceTexture) {
            if(frameAvailable != null){
                frameAvailable.onFrameAvailable(surfaceTexture);
            }
        }
    },mHandler);
}
  
//3:将第二步设置的Surface设置给到MediaCodec,设置完成即可启动解码
 private void initMediaCodec(){
        try {
            mMediaCodec = mMediaCodec.createDecoderByType("video/avc"); // 使用AVC解码器
            // 设置输出颜色格式为Surface ,videoMediaFormat还可以设置其他属性
            videoMediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatRGBFlexible); 
            //Surface是使用opengl的纹理创建出来的Surface,所以MediaCodec解码输出数据就输出到了opengl环境中创建的纹理上了
            mMediaCodec.configure(videoMediaFormat, mDecodeSurface, null, 0); // 将格式配置应用到解码器
            mMediaCodec.start();
        } catch (IOException e) {
        }
    }

前面启动了解码之后就需要启动解码,解码采用MediaCodec的同步解码方式,解码完成之后会将数据控制是否渲染到纹理上

arduino 复制代码
//解码完成,将数据根据PTS进行1s中的渲染,releaseOutputBuffer就会将数据释放,通知到 mVideoSurfaceTexture中的设置的Listener中
private void getOutPutBuffer(){
         if(!outputDone){
                info = new MediaCodec.BufferInfo();
                int decoderStatus = mMediaCodec.dequeueOutputBuffer(info, TIMEOUT_USEC);
                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    // no output available yet
                    Log.i(TAG, "no output from decoder available");
                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    // not important for us, since we're using Surface
                     Log.i(TAG, "decoder output buffers changed");
                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    MediaFormat newFormat = mMediaCodec.getOutputFormat();
                     Log.i(TAG, "decoder output format changed: " + newFormat);
                } else if (decoderStatus < 0) {
                    Log.i(TAG,"unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
                }else {
                    outputChunk++;
                     Log.i(TAG, outputChunk + " frame surface decoder given buffer " + decoderStatus +
                            " (size=" + info.size + ")" +",pts = " + info.presentationTimeUs);
                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        Log.i(TAG, "output EOS");
                        outputDone = true;
                    }
​
                   sleepRender(info, startDecodeTime);
                   boolean doRender = (info.size != 0);
                   mMediaCodec.releaseOutputBuffer(decoderStatus, doRender);
                }
            }
}
 private void sleepRender(MediaCodec.BufferInfo audioBufferInfo, long startMs) {
        long beging = System.currentTimeMillis() - startMs;
        while (audioBufferInfo.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs ) {
            try {
                // 10 毫秒
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
​

在收到帧可用的回调之后,对视频进行帧绘制,绘制的程序就是之前初始化好的 playVideoFilter中,playVideoFilter中会根据之前创建的需要的渲染方式进行逐帧绘制

ini 复制代码
    @Override
    public void onDrawFrame(GL10 gl) {
        if(!isUpdateVideo){
            Log.i(TAG,"onDrawFrame but flag is Wrong");
            return;
        }
        needUpdateSurfaceTexture.updateTexImage();
        needUpdateSurfaceTexture.getTransformMatrix(textureMatrix);
        playVideoFilter.setTextureTransformMatrix(textureMatrix);
        playVideoFilter.onDrawFrame(oesTextureId);
        isUpdateVideo= false;
    }

三:总结

编解码和Opengl实现关联的关键就是在Opengl的环境中创建好纹理,(在Android中使用的是OES纹理,不是普通的2D Texture)将此纹理作为入参创建Surface,并将Surface和Mediacodec关联起来就能实现MediaCodec解码,然后Opengl渲染 。但是Opengl程序的创建,以及MediaCodec的使用请关注另外的一片介绍

相关推荐
天天不在5513 天前
从拉代码到编出库,WebRTC 上的几道坎
音视频开发
刃若新发5 天前
Windows 用本地 MP4 文件搭建 RTSP/HTTP-FLV 播放环境指引
音视频开发
落魄Android在线炒饭7 天前
Android 实战问题反推 OpenGL 概念(上)
android·opengl
扶尔魔ocy13 天前
【qt openGL入门笔记】windows编译MinGW版本的osgearth(2.10.1)
qt·opengl·osg
屏什么录14 天前
录屏超过两小时就卡顿崩盘?从参数调优到代码实现,一文彻底搞定长时间录屏
前端·音视频开发
扶尔魔ocy14 天前
【QT OpenGL】知识点编目及传送门
qt·opengl·osg
字节跳动视频云技术团队15 天前
720P 直升 8K,解密 2026 抖音美好奇妙夜巨幕超分黑科技
人工智能·音视频开发
VimorphTech18 天前
文件名是 MP4 就能处理?用 ffprobe 检查视频流、音轨和时长
java·音视频开发
冬奇Lab21 天前
开源项目第242期:HyperFrames —— 写 HTML 就能渲染视频,专为 AI Agent 设计的确定性视频框架
人工智能·开源·音视频开发
屏什么录1 个月前
电脑如何录屏幕视频和声音?从工具到代码,7 种方案帮你彻底搞定
前端·音视频开发