Android MediaCodec 视频编码

从网上查阅了很多资料,貌似大部分人使用MediaCodec组件都是用来解码的,所以编码的资料非常少,也相对比较古老,具体的应用案例基本都停留在Bigflake那个sample上,而那个sample不说是个Unit Test , 其实本身  1) 不能同时保持预览和编码  2) 那段代码中是有bug的, 录制的视频时间轴是不对的 3)这个实现使用的是相对古老的同步获取buffer流程,性能较异步流程有所不如

这里记录一下我使用MediaCodec的经验

1.  从developer.android.com上查阅MediaCodec的资料,能够很显而易见的看到有同步和异步两种使用方式 , 这里不介绍同步,只介绍异步的使用,其实两种使用方式主体思想类似: 等待MediaCodec 提供的buffer ready , 将 采集到的数据填充到codec的buffer内,等待buffer处理,然后Codec会吐出处理完的数据,同步和异步的区别在于同步方法需要主动轮寻codec的当前状态,而异步是主动回调,因此异步理论上是要比同步效率高很多的,而且实现更加合理 。


2. 这里的举例是使用surface作为input,  Surface 在Android的多媒体编程中多作为bufferManager , 用来接收来自native的buffer

    note: surface作为input时, surface自己会管理buffer,并且将buffer送到codec内部,所以callback的onInputBufferAvailable内留白就行了, 而且永远不会被回调

mVideoCodec.setCallback(new MediaCodec.Callback() {
    @Override
    public void onInputBufferAvailable(MediaCodec codec, int index) {
        Log.w(TAG, "codec inputBuffer available");
    }

    @Override
    public void onOutputBufferAvailable(final MediaCodec codec, final int index, final MediaCodec.BufferInfo info) {
        final ByteBuffer outputBuffer = codec.getOutputBuffer(index);
        outputBuffer.position(info.offset);
        outputBuffer.limit(info.offset + info.size);
        // Sample codec-processed frame to muxer
        mMuxerHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mMuxerState == STATE_READY) {
                    if (mMuxer != null) {
                        if (mProgressListener != null) {
                            mProgressListener.onEncodeProgressUpdate(info.presentationTimeUs,mEncodingFile.getTotalSpace());
                        }
                        mMuxer.writeSampleData(mVideoTrackIndex, outputBuffer, info);
                    }
                } else {//Cache frames into buffer when muxer is not ready
                    VideoOutputCache cache = new VideoOutputCache();
                    byte[] rawData = new byte[info.size];
                    outputBuffer.get(rawData);
                    cache.buffer = ByteBuffer.wrap(rawData);
                    cache.info = info;
                    mOutputCache.add(cache);
                }
                codec.releaseOutputBuffer(index, false);
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    Log.w(TAG, "stop video");
                    codec.stop();
                    codec.release();
                    waitForMuxerStop(STATE_VIDEO_READY);
                }
            }
        });
    }
.....
}
 
3. 想要用Surface作为input, 那么需要有一个组件能够将camera的buffer送到surface上;
   同时,一旦送入surface,那么这一帧buffer就被使用掉了,内部的buffer队列会将使用掉的buffer回收供之后的帧使用,这样就有一个问题,camera1的只能绑定一块buffer作为输出目标,一旦将这块codec生成的surface绑定到android.hardware.camera组件上,那么帧数据会被codec使用掉,即使这个surface通过任何手段绑定到任何显示在屏幕上的控件(比如surfaceview/textureView),也不会有数据显示到控件上。
   这个问题可以有两种解决方案:
   1)Android的camera2 能够设置多个输出目标,用codec生成的surface可以直接添加到captureRequest的参数数组中; 
   2)就是我采用的方案,利用openGL,将surface绑定成为GLSurface ,同时,将用来显示的surface绑定成为另一个GLSurface ,每次渲染的时候不断切换两个surface渲染。当然我使用这个方案也是为了能够使用openGL做一些实时处理。 这里顺便提一下,显然draw两次是很慢的,需要做shader计算,等待两次GPU数据同步, 如果手机支持OpenGLES3.0 可以使用glBlitFrameBuffer的方法将帧copy到FBO ,这样每次只需要做单纯的内存copy就行了,实测性能明显上升

4. 录制视频需要为每一帧添加一个时间戳,Bigflake的sample中也实现了帧的timestamp标记,只是bigflake标记错了,时间戳记录的是视频内部的时间,我这里贴一下我的实现:
if(!EGL14.eglMakeCurrent(mGLDisplay,mGLSurface,mGLSurface,mGLContext)){
    CustGLException.buildEGLException("GL make current failed");
}
mGLRenderer.renderingFrame(texture, mWidth, mHeight,recorderSurfaceArea);



EGL14.eglSwapBuffers(mGLDisplay, mGLSurface);

if(mRecorderSurface!=EGL14.EGL_NO_SURFACE){


    //To ensure the final data is not interpolated , we use the higher resolution surface to generate FBO

    
    if(!EGL14.eglMakeCurrent(mGLDisplay,mRecorderSurface,mRecorderSurface,mGLContext)){
        CustGLException.buildEGLException("GL make current failed");
    }
    if(recorderSurfaceArea!=null) {
        mGLRenderer.renderingFrameToRecorder(recorderSurfaceArea.width(), recorderSurfaceArea.height(),mWidth,mHeight);
    }else{
        mGLRenderer.renderingFrameToRecorder(mWidth, mHeight,mWidth,mHeight);
    }
    EGLExt.eglPresentationTimeANDROID(mGLDisplay,mRecorderSurface,texture.getTimestamp()-mStartTime);//这里需要注意,要减去startTime
    EGL14.eglSwapBuffers(mGLDisplay, mRecorderSurface);
}else{
    mStartTime=texture.getTimestamp();
}

 上述内容实现了Codec的监听处理,帧的收集和显示,Muxer部分的采样资料太多这里不展示了,有一定基础的朋友可以很轻易的利用上述自己实现Recorder了 

     

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值