Android利用硬解硬编和OpenGLES来高效的处理MP4视频

原文链接如下:

Android利用硬解硬编和OpenGLES来高效的处理MP4视频\

源码

AAVT

参考文档

Android硬编码——音频编码、视频编码及音视频混合

OpenGLES

视频处理问题:

  1. 处理过程耗时太长.

    之前的思路:

    MediaCodec 解码,取出 ByteBuffer ,然后用 OpenGLES 处理,处理完成之后, 通过 readPixels , 得到图像数据, 然后将图像数据再推入 MediaCodec 进行编码.

    在这里readPixels非常耗时,480x840的视频,一帧耗时基本是40ms+.

  2. 手机兼容性问题.

    各个Android手机硬解码出来的数据格式不尽相同.虽然大多数手机都支持YUV420PYUV420SP ,但是也有些奇葩手机,只能解码出OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar32m这类的格式 ]

参照官网文档,可以通过 MediaCodec 解码视频支持解码到 Surface 上,编码也可以直接从 Surface 采集数据,这样的话,视频数据可以直接解码到 Surface 上,然后通过 OpenGLES 处理,再通过 Surface 进行编码.无需关注解码出来的数据格式. 免得将 GPU数据拷贝到内存进行处理,再传回 GPU .

处理流程:

  1. 利用 MediaExtractor 获取Mp4的音频轨和视频轨,获得其 MediaFormat .

  2. 根据音视频信息,创建视频解码器,其中,视频解码器的Surface是先通过创建一个SurfaceTexture,然后将这个SurfaceTexture作为参考创建的,这样的话,视频流可以通过这个SurfaceTexture 提供给 OpenGL 环境作为 输出.

    视频编码器的Surface可用 createInputSurface() 方法创建.这个Surface 后传递给 OpenGL 环境作为输出.

  3. 创建 MediaMuxer . 用于后面合成 处理完的视频与音频.

  4. 创建 OpenGL 环境. 用于处理视频图像,这个 OpenGL 环境由 EGL 创建, EGLSurfaceWindowSurface ,并以编码器创建的Surface 作为参数.

  5. MediaExtractor 读取原始Mp4中的视频流,交于解码器解码至 Surface 上.

  6. SurfaceTexture 监听有视频帧时,通知 OpenGL 线程工作,处理视频图像,并渲染.

  7. OpenGL 每次渲染完毕,通知编码线程进行编码,编码后的数据通过 MediaMuxer 混合.

  8. 视频流处理完毕后,利用 MediaExtractor 读取音频流,并利用MediaMuxer 混合到新的视频文件中 .

  9. 处理完毕后调用 MediaMuxer 的 stop方法,处理后的视频就生成成功.

具体实现:

根据以上流程,进行代码实现.

创建编解码工具

这一步直接完成了 1,2,3 步的工作

//todo 获取视频旋转信息,并做出相应处理MediaMetadataRetriever mMetRet=new MediaMetadataRetriever();mMetRet.setDataSource(mInputPath);mExtractor=new MediaExtractor();mExtractor.setDataSource(mInputPath);int count=mExtractor.getTrackCount();// 解析MP4for (int i=0;i<count;i++){         MediaFormat format=mExtractor.getTrackFormat(i);    String mime=format.getString(MediaFormat.KEY_MIME);    if(mime.startsWith("audio")){             mAudioDecoderTrack=i;    }else if(mime.startsWith("video")){     mVideoDecoderTrack=i;        mInputVideoWidth=format.getInteger(MediaFormat.KEY_WIDTH);        mInputVideoHeight=format.getInteger(MediaFormat.KEY_HEIGHT);        //创建解码器        mVideoDecoder=MediaCodec.createDecoderByType(mime);                mVideoTextureId=mEGLHelper.createTextureID();        //注意这里,创建了一个SurfaceTexture        mVideoSurfaceTexture=new SurfaceTexture(mVideoTextureId);        mVideoSurfaceTexture.setOnFrameAvailableListener(mFrameAvaListener);        //将SurfaceTexture作为参数创建一个Surface,用来接收解码视频流        mVideoDecoder.configure(format,new Surface(mVideoSurfaceTexture),null,0);        if(!isRenderToWindowSurface){                 if(mOutputVideoWidth==0||mOutputVideoHeight==0){                     mOutputVideoWidth=mInputVideoWidth;                mOutputVideoHeight=mInputVideoHeight;            }            MediaFormat videoFormat=MediaFormat.createVideoFormat(mime,mOutputVideoWidth,mOutputVideoHeight);            videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);            videoFormat.setInteger(MediaFormat.KEY_BIT_RATE,mOutputVideoHeight*mOutputVideoWidth*5);            videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 24);            videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);                     mVideoEncoder=MediaCodec.createEncoderByType(mime);            mVideoEncoder.configure(videoFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);            //注意这里,创建了一个Surface,这个Surface是编码器的输入,也是OpenGL环境的输出            mOutputSurface=mVideoEncoder.createInputSurface();            Bundle bundle=new Bundle();            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {                     bundle.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE,mOutputVideoHeight*mOutputVideoWidth*5);                mVideoEncoder.setParameters(bundle);            }        }    }}//这里的if是测试时候,直接解码到屏幕上,外部设置了OutputSurface,用于测试,所以不必管if(!isRenderToWindowSurface){         //如果用户没有设置渲染到指定Surface,就需要导出视频,暂时不对音频做处理    mMuxer=new MediaMuxer(mOutputPath,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);    MediaFormat format=mExtractor.getTrackFormat(mAudioDecoderTrack);    mAudioEncoderTrack=mMuxer.addTrack(format);}

创建OpenGL环境

第4步,创建OpenGL环境.用来处理视频图像.先直接贴个工具类.用于创建OpenGL环境

public class EGLHelper {         private EGLSurface mEGLSurface;    private EGLContext mEGLContext;    private EGLDisplay mEGLDisplay;    private EGLConfig mEGLConfig;    private EGLContext mShareEGLContext=EGL14.EGL_NO_CONTEXT;    private boolean isDebug=true;    private int mEglSurfaceType=EGL14.EGL_WINDOW_BIT;    private Object mSurface;    /**     * @param type one of {@link EGL14#EGL_WINDOW_BIT}、{@link EGL14#EGL_PBUFFER_BIT}、{@link EGL14#EGL_PIXMAP_BIT}     */    public void setEGLSurfaceType(int type){             this.mEglSurfaceType=type;    }    public void setSurface(Object surface){             this.mSurface=surface;    }    /**     * create the environment for OpenGLES     * @param eglWidth width     * @param eglHeight height     */    public boolean createGLES(int eglWidth, int eglHeight){             int[] attributes = new int[] {                     EGL14.EGL_SURFACE_TYPE, mEglSurfaceType,      //渲染类型                EGL14.EGL_RED_SIZE, 8,  //指定RGB中的R大小(bits)                EGL14.EGL_GREEN_SIZE, 8, //指定G大小                EGL14.EGL_BLUE_SIZE, 8,  //指定B大小                EGL14.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四项实际上指定了像素格式                EGL14.EGL_DEPTH_SIZE, 16, //指定深度缓存(Z Buffer)大小                EGL14.EGL_RENDERABLE_TYPE, 4, //指定渲染api类别, 如上一小节描述,这里或者是硬编码的4(EGL14.EGL_OPENGL_ES2_BIT)                EGL14.EGL_NONE };  //总是以EGL14.EGL_NONE结尾        int glAttrs[] = {                     EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,  //0x3098是EGL14.EGL_CONTEXT_CLIENT_VERSION,但是4.2以前没有EGL14                EGL14.EGL_NONE        };        int bufferAttrs[]={                     EGL14.EGL_WIDTH,eglWidth,                EGL14.EGL_HEIGHT,eglHeight,                EGL14.EGL_NONE        };        //获取默认显示设备,一般为设备主屏幕        mEGLDisplay= EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);        //获取版本号,[0]为版本号,[1]为子版本号        int[] versions=new int[2];        EGL14.eglInitialize(mEGLDisplay,versions,0,versions,1);        log(EGL14.eglQueryString(mEGLDisplay, EGL14.EGL_VENDOR));        log(EGL14.eglQueryString(mEGLDisplay, EGL14.EGL_VERSION));        log(EGL14.eglQueryString(mEGLDisplay, EGL14.EGL_EXTENSIONS));        //获取EGL可用配置        EGLConfig[] configs = new EGLConfig[1];        int[] configNum = new int[1];        EGL14.eglChooseConfig(mEGLDisplay, attributes,0, configs,0, 1, configNum,0);        if(configs[0]==null){                 log("eglChooseConfig Error:"+ EGL14.eglGetError());            return false;        }        mEGLConfig = configs[0];        //创建EGLContext        mEGLContext= EGL14.eglCreateContext(mEGLDisplay,mEGLConfig,mShareEGLContext, glAttrs,0);        if(mEGLContext==EGL14.EGL_NO_CONTEXT){                 return false;        }        //获取创建后台绘制的Surface        switch (mEglSurfaceType){                 case EGL14.EGL_WINDOW_BIT:                mEGLSurface=EGL14.eglCreateWindowSurface(mEGLDisplay,mEGLConfig,mSurface,new int[]{     EGL14.EGL_NONE},0);                break;            case EGL14.EGL_PIXMAP_BIT:                break;            case EGL14.EGL_PBUFFER_BIT:                mEGLSurface=EGL14.eglCreatePbufferSurface(mEGLDisplay,mEGLConfig,bufferAttrs,0);                break;        }        if(mEGLSurface==EGL14.EGL_NO_SURFACE){                 log("eglCreateSurface Error:"+EGL14.eglGetError());            return false;        }        if(!EGL14.eglMakeCurrent(mEGLDisplay,mEGLSurface,mEGLSurface,mEGLContext)){                 log("eglMakeCurrent Error:"+EGL14.eglQueryString(mEGLDisplay,EGL14.eglGetError()));            return false;        }        log("gl environment create success");        return true;    }    public void setShareEGLContext(EGLContext context){             this.mShareEGLContext=context;    }    public EGLContext getEGLContext(){             return mEGLContext;    }    public boolean makeCurrent(){             return EGL14.eglMakeCurrent(mEGLDisplay,mEGLSurface,mEGLSurface,mEGLContext);    }    public boolean destroyGLES(){             EGL14.eglMakeCurrent(mEGLDisplay,EGL14.EGL_NO_SURFACE,EGL14.EGL_NO_SURFACE,EGL14.EGL_NO_CONTEXT);        EGL14.eglDestroySurface(mEGLDisplay,mEGLSurface);        EGL14.eglDestroyContext(mEGLDisplay,mEGLContext);        EGL14.eglTerminate(mEGLDisplay);        log("gl destroy gles");        return true;    }    public void setPresentationTime(long time){             EGLExt.eglPresentationTimeANDROID(mEGLDisplay,mEGLSurface,time);    }    public boolean swapBuffers(){             return EGL14.eglSwapBuffers(mEGLDisplay,mEGLSurface);    }    //创建视频数据流的OES TEXTURE    public int createTextureID() {             int[] texture = new int[1];        GLES20.glGenTextures(1, texture, 0);        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);        return texture[0];    }    private void log(String log){             if(isDebug){                 Log.e("EGLHelper",log);        }    }}

借助上面的工具类创建OpenGL环境. 可以看到里面使用了信号量.是用于当有新的视频图像时由SurfaceTexture的监听器通知GL线程执行渲染.没有的话就等待新的视频图像解码完后再执行处理工作

mSem=new Semaphore(0);//设置输出的SurfacemEGLHelper.setSurface(mOutputSurface);//根据设置的输出视频的宽高创建OpenGL环境boolean ret=mEGLHelper.createGLES(mOutputVideoWidth,mOutputVideoHeight);if(!ret)return;mRenderer.onCreate(mOutputVideoWidth,mOutputVideoHeight);while (mGLThreadFlag){         try {             mSem.acquire();    } catch (InterruptedException e) {             e.printStackTrace();    }    mVideoSurfaceTexture.updateTexImage();    //回调用户的处理函数    mRenderer.onDraw();    //设置时间点,用于输出视频图像的时间点,这里是填入输入视频的时间点    mEGLHelper.setPresentationTime(mVideoDecoderBufferInfo.presentationTimeUs*1000);    if(!isRenderToWindowSurface){             //调用编码函数进行编码        videoEncodeStep(false);    }    mEGLHelper.swapBuffers();}if(!isRenderToWindowSurface){         //编码视频,传入true表示视频结束    videoEncodeStep(true);}//销毁OpenGL环境mEGLHelper.destroyGLES();mRenderer.onDestroy();

渲染

第6步是用于通知这个GL线程执行渲染工作.只需要在监听器中.发出信号就可以了。

private SurfaceTexture.OnFrameAvailableListener mFrameAvaListener=new SurfaceTexture.OnFrameAvailableListener() {             @Override        public void onFrameAvailable(SurfaceTexture surfaceTexture) {                 mSem.release();        }    };

视频流解码

第5步,需要将视频解码,解码的方法如下.

在解码的线程中循环调用此方法,其返回值为true时结束循环,也就是视频帧解码完毕.

//视频解码到SurfaceTexture上,以供后续处理。返回值为是否是最后一帧视频private boolean videoDecodeStep(){         int mInputIndex=mVideoDecoder.dequeueInputBuffer(TIME_OUT);    if(mInputIndex>=0){             ByteBuffer buffer=getInputBuffer(mVideoDecoder,mInputIndex);        buffer.clear();        synchronized (Extractor_LOCK) {                 mExtractor.selectTrack(mVideoDecoderTrack);            int ret = mExtractor.readSampleData(buffer, 0);            if (ret != -1) {                     mVideoDecoder.queueInputBuffer(mInputIndex, 0, ret, mExtractor.getSampleTime(), mExtractor.getSampleFlags());            }            isVideoExtractorEnd = !mExtractor.advance();        }    }    while (true){             int mOutputIndex=mVideoDecoder.dequeueOutputBuffer(mVideoDecoderBufferInfo,TIME_OUT);        if(mOutputIndex>=0){                 mVideoDecoder.releaseOutputBuffer(mOutputIndex,true);        }else if(mOutputIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){                 MediaFormat format=mVideoDecoder.getOutputFormat();        }else if(mOutputIndex==MediaCodec.INFO_TRY_AGAIN_LATER){                 break;        }    }    return isVideoExtractorEnd;}

视频流编码并混合

private boolean videoEncodeStep(boolean isEnd){         if(isEnd){             mVideoEncoder.signalEndOfInputStream();    }    while (true){             int mOutputIndex=mVideoEncoder.dequeueOutputBuffer(mVideoEncoderBufferInfo,TIME_OUT);        if(mOutputIndex>=0){                 ByteBuffer buffer=getOutputBuffer(mVideoEncoder,mOutputIndex);            if(mVideoEncoderBufferInfo.size>0){                     mMuxer.writeSampleData(mVideoEncoderTrack,buffer,mVideoEncoderBufferInfo);            }            mVideoEncoder.releaseOutputBuffer(mOutputIndex,false);        }else if(mOutputIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){                 MediaFormat format=mVideoEncoder.getOutputFormat();            mVideoEncoderTrack=mMuxer.addTrack(format);            mMuxer.start();            synchronized (MUX_LOCK){                     MUX_LOCK.notifyAll();            }        }else if(mOutputIndex==MediaCodec.INFO_TRY_AGAIN_LATER){                 break;        }    }    return false;}

音频流处理

因为现在暂时不需要对视音频处理.所以直接从原始MP4中读取音频流混合到新的Mp4中即可.与解码相同.这个方法也是在线程中循环调用.返回true时终止循环,最后调用MediaMuxer的stop方法.新的视频就生成好.

private boolean audioDecodeStep(ByteBuffer buffer){         buffer.clear();    synchronized (Extractor_LOCK){             mExtractor.selectTrack(mAudioDecoderTrack);        int length=mExtractor.readSampleData(buffer,0);        if(length!=-1){                 int flags=mExtractor.getSampleFlags();            mAudioEncoderBufferInfo.size=length;            mAudioEncoderBufferInfo.flags=flags;            mAudioEncoderBufferInfo.presentationTimeUs=mExtractor.getSampleTime();            mAudioEncoderBufferInfo.offset=0;            mMuxer.writeSampleData(mAudioEncoderTrack,buffer,mAudioEncoderBufferInfo);        }        isAudioExtractorEnd=!mExtractor.advance();    }    return isAudioExtractorEnd;}

为了不阻塞主线程.音视频的处理单独开一个线程处理为好.

mDecodeThread=new Thread(new Runnable() {         @Override    public void run() {             //视频处理        while (mCodecFlag&&!videoDecodeStep());        mGLThreadFlag=false;        try {                 mGLThread.join();        } catch (InterruptedException e) {                 e.printStackTrace();        }        //将原视频中的音频复制到新视频中        ByteBuffer buffer=ByteBuffer.allocate(1024*32);        while (!audioDecodeStep(buffer));        buffer.clear();        mMuxer.stop();        if(mCompleteListener!=null){                 mCompleteListener.onComplete(mOutputPath);        }    }});

更多相关文章

  1. Rexsee API介绍:Android照片、视频拍摄,Camera扩展
  2. Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
  3. android可拉伸图片处理(.9.png)格式的图片
  4. Android条码扫描二维码扫描——ZXing android 源码简化
  5. Android对emoji表情的处理
  6. Android(安卓)NDK开发技巧一
  7. 转android123 预防Android内存泄露
  8. android的消息处理机制(图+源码分析)——Looper,Handler,Message
  9. 关于Android捕捉异常崩溃日志的Crash

随机推荐

  1. android 开发零起步学习笔记(二十五):Androi
  2. Android(Lollipop/5.0) Material Design(
  3. Android学习第一章第一节android学习路线
  4. ANDROID 控件常用属性
  5. Android隐藏状态栏和标题栏,相当于全屏效
  6. android:hintText与android:inputType详
  7. Android(安卓)WebView与Js的交互
  8. Android(Lollipop/5.0) Material Design(
  9. .Net 转战 Android(安卓)4.4 日常笔记目
  10. Android系统自带样式(@android:style/)