Android利用硬解硬编和OpenGLES来高效的处理MP4视频
Android利用硬解硬编和OpenGLES来高效的处理MP4视频
原文链接如下:
Android利用硬解硬编和OpenGLES来高效的处理MP4视频\
源码
AAVT
参考文档
Android硬编码——音频编码、视频编码及音视频混合
OpenGLES
视频处理问题:
-
处理过程耗时太长.
之前的思路:
用
MediaCodec
解码,取出ByteBuffer
,然后用OpenGLES
处理,处理完成之后, 通过readPixels
, 得到图像数据, 然后将图像数据再推入MediaCodec
进行编码.在这里readPixels非常耗时,480x840的视频,一帧耗时基本是40ms+.
-
手机兼容性问题.
各个Android手机硬解码出来的数据格式不尽相同.虽然大多数手机都支持
YUV420P
或YUV420SP
,但是也有些奇葩手机,只能解码出OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar32m
这类的格式 ]
参照官网文档,可以通过 MediaCodec
解码视频支持解码到 Surface
上,编码也可以直接从 Surface
采集数据,这样的话,视频数据可以直接解码到 Surface
上,然后通过 OpenGLES
处理,再通过 Surface
进行编码.无需关注解码出来的数据格式. 免得将 GPU
数据拷贝到内存进行处理,再传回 GPU
.
处理流程:
-
利用
MediaExtractor
获取Mp4的音频轨和视频轨,获得其MediaFormat
. -
根据音视频信息,创建视频解码器,其中,视频解码器的Surface是先通过创建一个SurfaceTexture,然后将这个SurfaceTexture作为参考创建的,这样的话,视频流可以通过这个
SurfaceTexture
提供给OpenGL
环境作为 输出.视频编码器的Surface可用
createInputSurface()
方法创建.这个Surface
后传递给OpenGL
环境作为输出. -
创建
MediaMuxer
. 用于后面合成 处理完的视频与音频. -
创建
OpenGL
环境. 用于处理视频图像,这个OpenGL
环境由EGL
创建,EGLSurface
为WindowSurface
,并以编码器创建的Surface
作为参数. -
MediaExtractor
读取原始Mp4中的视频流,交于解码器解码至Surface
上. -
SurfaceTexture
监听有视频帧时,通知OpenGL
线程工作,处理视频图像,并渲染. -
OpenGL
每次渲染完毕,通知编码线程进行编码,编码后的数据通过MediaMuxer
混合. -
视频流处理完毕后,利用
MediaExtractor
读取音频流,并利用MediaMuxer
混合到新的视频文件中 . -
处理完毕后调用
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); } }});
更多相关文章
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Rexsee API介绍:Android照片、视频拍摄,Camera扩展
- Android开发实践:线程与异步任务
- android线程间通信之handler
- Android中子线程真的不能更新UI吗?
- Android+Jquery Mobile学习系列(1)-开发环境