最近在学习ing,加油硬件和软件编解码器 在介绍之前,我们需要知道什么是硬编解码器和软编解码器? 1。软编解码器:使用软件本身或使用CPU的方式对原始视频进行编解码。 优点:兼容性好。。 缺点:CPU占用率高,app内存占用较高,可能是因为CPU发热导致降频、卡顿、无法流畅录制、播放视频等。 2。硬编解码器:使用非CPU编码,如显卡GPU、专用DSP芯片、厂商芯片等。一般编解码算法是固定的,所以采用芯片处理。 优点:非常快速高效,CPU占用率低,即使长时间录制高清视频,手机也不会发热。 缺点:但是兼容性不好,经常出现画面不够精细的问题(但是看不出来)。MediaCodec硬编解码器 一般安卓直播采集终端短视频编辑软件默认使用硬编解码,如果手机不支持软编解码。硬编解码器为王。 在AndroidMediaCodec类中使用的是编码和解码。MediaCodec它是什么??MediaCodec是Android音视频编解码类,它通过访问底层codec来实现编解码的功能,比如需要把摄像头的视频yuv数据编码为h264h265,pcm编码为aac,h264h265解码等待yuv,aac解码pcm等待。MediaCodec是Android4。1API16引入了,在Android5。0API21中加入了异步模式。 MediaCodecCall是系统注册的编解码器,硬件厂商在系统中注册自己的硬编解码器,称为硬编解码器,如果硬件厂商注册软件编解码器,就是软件编解码器。通常不同的硬件制造商是不同的。然后MediaCodec负责调用。获取手机支持的编解码器 不同的手机支持的编解码器不同,如何获取手机支持的编解码器?如下:RequiresApi(Build。VERSIONCODES。LOLLIPOP)privatefungetSupportCodec(){vallistMediaCodecList(MediaCodecList。REGULARCODECS)valcodecslist。codecInfosLog。d(TAG,Decoders:)for(codecincodecs){if(!codec。isEncoder)Log。d(TAG,codec。name)}Log。d(TAG,Encoders:)for(codecincodecs){if(codec。isEncoder)Log。d(TAG,codec。name)}} 输出2020122519:16:00。9141311513115com。bj。gxz。h264decoderdemoDH264:Decoders:2020122519:16:00。9141311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。aac。decoder2020122519:16:00。9141311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。amrnb。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。amrwb。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。flac。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。g711。alaw。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。g711。mlaw。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。gsm。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。mp3。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。opus。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。raw。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。vorbis。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。hisi。video。decoder。avc2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。h264。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。h263。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。hisi。video。decoder。hevc2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。hevc。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。hisi。video。decoder。mpeg22020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。hisi。video。decoder。mpeg42020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。mpeg4。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。hisi。video。decoder。vp82020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。vp8。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。vp9。decoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:Encoders:2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。aac。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。amrnb。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。amrwb。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。flac。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。hisi。video。encoder。avc2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。h264。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。h263。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。hisi。video。encoder。hevc2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。mpeg4。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。vp8。encoder2020122519:16:00。9151311513115com。bj。gxz。h264decoderdemoDH264:OMX。google。vp9。encoder 看一下命名,软解码器通常都是OMX。google以开头的,就像上面那个一样OMX。google。h264。decoder。硬解码器是基于OMX。〔hardwarevendor〕一开始的,像上面那个应该是海思芯片OMX。hisi。video。decoder。avc。hisi当然也有一些不遵守这个规则的,系统会认为它是软解码器。编码器的名称是相同的。来自Android系统的源码可以判断规则, 源码地址:http:androidos。net。cnandroid6。0。1r16xrefframeworksavmedialibstagefrightOMXCodec。cppstaticboolIsSoftwareCodec(constcharcomponentName){if(!strncmp(OMX。google。,componentName,11)){returntrue;}if(!strncmp(OMX。,componentName,4)){returnfalse;}returntrue;}MediaCodec处理数据的类型 MediaCodec非常强大,支持的编解码数据类型有:压缩音频数据、压缩视频数据、原始音频数据和原始视频数据,并且支持不同封装格式的编解码,如前所述,如果是硬解码,当然,也需要手机厂商的支持。您可以通过设置Surface来获取呈现原始视频数据。MediaCodec关于API每个方法和参数都有自己的含义。你可以慢慢深入地使用它。MediaCodec的编解码过程 下图是Android官方文档提供的,官方文档很详细。 https:developer。android。google。cnreferenceandroidmediaMediaCodec?hlen MediaCodec处理输入数据产生输出数据,异步处理数据时,使用一组输入输出ByteBuffer。该过程通常是将数据填充到预设的输入缓冲区(ByteBuffer)中,输入缓冲区填满数据后,传递给MediaCodec的编码和解码处理。经过编码和解码后,它会在。然后用户可以得到编码和解码后的数据,然后将ByteBuffer释放回MediaCodec,往复循环。 需要注意的是,Buffer并不是我们自己对MediaCodec的新对象,它是MediaCodec为了更好地控制Buffer来处理,我们需要使用MediaCodec提供的方法来获取,然后将数据插入其中并获取数据。媒体编解码器APIMediaCodec的创建createDecoderByTypecreateEncoderByType:根据具体的MIME类型(如videoavc)建立codec。Decoder就是decoder,Encoder就是encoder。createByCodecName:知道组件的确切名称(如OMX。google。h264。decoder)时,从组件名称编解码器创建。使用MediaCodecList可以获取组件的名称,如上所述。configure:配置解码器或编码器。比如可以配置解码后的数据通过surface来显示,本文下面部分是解码h264的demo配置surface来把yuv的数据渲染到surface上。start:开始编解码,等待数据。数据处理,开始编解码dequeueInputBuffer:返回一个有效的输入缓冲区的索引queueInputBuffer:输入流入队列。通常是用数据填充它dequeueOutputBuffer:从输出队列中取出编码解码数据,如果输入的数据较多,可能需要循环读取,一般在写代码的时候需要调用循环releaseOutputBuffer:释放ByteBuffer数据返回给MediaCodecgetInputBuffers:获取需要对数据进行编码和解码的输入流队列,返回一个ByteBufferArraygetOutputBuffers:获取编解码后的数据输出流队列,返回一个ByteBuffer数组flush:清空输入输出队列缓冲区stop:停止编解码release:发布编解码器 从上面的api我可以看到电影MediaCodec编解码器API的生命周期,可以再看官网。MediaCodec的同步和异步编解码器同步方式 官方样本MediaCodeccodecMediaCodec。createByCodecName(name);codec。configure(format,);MediaFormatoutputFormatcodec。getOutputFormat();optionBcodec。start();for(;;){intinputBufferIdcodec。dequeueInputBuffer(timeoutUs);if(inputBufferId0){ByteBufferinputBuffercodec。getInputBuffer();fillinputBufferwithvaliddatacodec。queueInputBuffer(inputBufferId,);}intoutputBufferIdcodec。dequeueOutputBuffer();if(outputBufferId0){ByteBufferoutputBuffercodec。getOutputBuffer(outputBufferId);MediaFormatbufferFormatcodec。getOutputFormat(outputBufferId);optionAbufferFormatisidenticaltooutputFormatoutputBufferisreadytobeprocessedorrendered。codec。releaseOutputBuffer(outputBufferId,);}elseif(outputBufferIdMediaCodec。INFOOUTPUTFORMATCHANGED){Subsequentdatawillconformtonewformat。CanignoreifusinggetOutputFormat(outputBufferId)outputFormatcodec。getOutputFormat();optionB}}codec。stop();codec。release(); 流程如下:CreateandconfigureMediaCodecobjectLoopuntilitsdone:IfinputbufferBeonitReadapieceofinput,FillitinwithinputbufferEncodinganddecodingareperformedinthesystemIftheoutputbufferBeonit:FromoutputbufferGetthedataafterencodinganddecodingforprocessing。Afterprocessing,ThedestructionMediaCodecobject。异步方式 在Android5。0,API21,引入异步模式。官方样品:MediaCodeccodecMediaCodec。createByCodecName(name);MediaFormatmOutputFormat;membervariablecodec。setCallback(newMediaCodec。Callback(){OverridevoidonInputBufferAvailable(MediaCodecmc,intinputBufferId){ByteBufferinputBuffercodec。getInputBuffer(inputBufferId);fillinputBufferwithvaliddatacodec。queueInputBuffer(inputBufferId,);}OverridevoidonOutputBufferAvailable(MediaCodecmc,intoutputBufferId,){ByteBufferoutputBuffercodec。getOutputBuffer(outputBufferId);MediaFormatbufferFormatcodec。getOutputFormat(outputBufferId);optionAbufferFormatisequivalenttomOutputFormatoutputBufferisreadytobeprocessedorrendered。codec。releaseOutputBuffer(outputBufferId,);}OverridevoidonOutputFormatChanged(MediaCodecmc,MediaFormatformat){Subsequentdatawillconformtonewformat。CanignoreifusinggetOutputFormat(outputBufferId)mOutputFormatformat;optionB}OverridevoidonError(){}});codec。configure(format,);mOutputFormatcodec。getOutputFormat();optionBcodec。start();waitforprocessingtocompletecodec。stop();codec。release();CreateandconfigureMediaCodecobject。toMediaCodecObjectsettingscallbackMediaCodec。CallbackstayonInputBufferAvailableCallingback:Readapieceofinput,FillitinwithinputbufferEncodinganddecodingareperformedinthesystemstayonOutputBufferAvailableCallingback:FromoutputbufferAfterencodinganddecoding,thedataisprocessed。Afterprocessing,ThedestructionMediaCodecobject。解码h264视频 让我们解码视频中的一个h264(摘自tiktok的一段话。葵h264文件)。h265是一回事,只要了解h264。h265的编码方式和原理以及码流结构,都是小菜一碟。为了更好地理解h264的比特流数据,我们演示了一次只读取内存中的文件byte数据。 我们从两个方面来处理,都可以正常播放,只是我们对h264Stream数据有了更深入的了解。就是我们做的h264比特流结构,一次一个的NAL单元(NALU)偷偷给MediaCodec,包括第一个SPS,PPS。是的,我们只是截取几个k,然后用MediaCodec填充它 首先初始化MediaCodecvarbytes:ByteArray?nullvarmediaCodec:MediaCodecinit{demotest,TofacilitateonetimeaccesstomemorybytesFileUtil。getBytes(path)videoavcNamelyH264,CreatedecodermediaCodecMediaCodec。createDecoderByType(videoavc)valmediaFormatMediaFormat。createVideoFormat(videoavc,width,height)mediaFormat。setInteger(MediaFormat。KEYFRAMERATE,15)mediaCodec。configure(mediaFormat,surface,null,0)} 方式一:划分NAL单元(NALU)方式privatefundecodeSplitNalu(){if(bytesnull){return}DatastartsubscribingvarstartFrameIndex0valtotalSizeIndexbytes!!。size1Log。i(TAG,totalSizetotalSizeIndex)valinputBuffersmediaCodec。inputBuffersvalinfoMediaCodec。BufferInfo()while(true){1ms1000ussubtlevalinIndexmediaCodec。dequeueInputBuffer(10000)if(inIndex0){Splitaframeofdataif(totalSizeIndex0startFrameIndextotalSizeIndex){Log。e(TAG,startIndextotalSize1,break)break}valnextFrameStartIndex:IntfindNextFrame(bytes!!,startFrameIndex1,totalSizeIndex)if(nextFrameStartIndex1){Log。e(TAG,nextFrameStartIndex1break)break}FillinthedatavalbyteBufferinputBuffers〔inIndex〕byteBuffer。clear()byteBuffer。put(bytes!!,startFrameIndex,nextFrameStartIndexstartFrameIndex)mediaCodec。queueInputBuffer(inIndex,0,nextFrameStartIndexstartFrameIndex,0,0)startFrameIndexnextFrameStartIndex}varoutIndexmediaCodec。dequeueOutputBuffer(info,10000)while(outIndex0){Heresasimpletemporalwaytokeepthevideoalivefps,OtherwisethevideowillplayveryfastdemoOfH264Fileis30fpstry{sleep(33)}catch(e:InterruptedException){e。printStackTrace()}Parameters2RendertosurfaceOn,surfaceNamelymediaCodec。configureParametersof2mediaCodec。releaseOutputBuffer(outIndex,true)outIndexmediaCodec。dequeueOutputBuffer(info,0)}}} NALU分割方法privatefunfindNextFrame(bytes:ByteArray,startIndex:Int,totalSizeIndex:Int):Int{for(iinstartIndex。。totalSizeIndex){00000001H264Startcodeforif(bytes〔i〕。toInt()0x00bytes〔i1〕。toInt()0x00bytes〔i2〕。toInt()0x00bytes〔i3〕。toInt()0x01){Log。e(TAG,bytes〔i4〕0X{Integer。toHexString(bytes〔i4〕。toInt())})Log。e(TAG,bytes〔i4〕{(bytes〔i4〕。toInt()。and(0X1F))})returni000001H264Startcodefor}elseif(bytes〔i〕。toInt()0x00bytes〔i1〕。toInt()0x00bytes〔i2〕。toInt()0x01){Log。e(TAG,bytes〔i3〕0X{Integer。toHexString(bytes〔i3〕。toInt())})Log。e(TAG,bytes〔i3〕{(bytes〔i3〕。toInt()。and(0X1F))})returni}}return1} 方式一:固定字节数据填充privatefunfindNextFrameFix(bytes:ByteArray,startIndex:Int,totalSizeIndex:Int):Int{Everytime,itsbettertomakethedatabigger,Otherwise,itslikeaweaknetwork,SlowdataflowleadstovideocardvallenstartIndex40000returnif(lentotalSizeIndex)totalSizeIndexelselen} 说明:在实际项目中,通常是web数据流塞进去的方式,只是给大家demo演示MediaCodec解码h264文件播放。保存decodeh264视频yuv数据为图片 我们保存在哪里呢,前面说了,解码后一定是h264保存,解码后的数据就是yuv数据。也就是说dequeueOutputBuffer然后取出解码后的数据,然后使用YuvImageClasscompressToJpegSaveasJpegJust图片。我们3s保持一个。 本地代码:3sSaveapictureif(System。currentTimeMillis()saveImage3000){saveImageSystem。currentTimeMillis()valbyteBuffer:ByteBuffermediaCodec。outputBuffers〔outIndex〕byteBuffer。position(info。offset)byteBuffer。limit(info。offsetinfo。size)valbaByteArray(byteBuffer。remaining())byteBuffer。get(ba)try{valparentFile(Environment。getExternalStorageDirectory()。absolutePathh264pic)if(!parent。exists()){parent。mkdirs()Log。d(TAG,parent{parent。absolutePath})}takeNV21Formatpicture,Withquality70CompressedintoJpegvalpath{parent。absolutePath}{System。currentTimeMillis()}frame。jpgLog。e(TAG,path:path)valfosFileOutputStream(File(path))valyuvImageYuvImage(ba,ImageFormat。NV21,width,height,null)yuvImage。compressToJpeg(Rect(0,0,yuvImage。getWidth(),yuvImage。getHeight()),80,fos)fos。flush()fos。close()}catch(e:IOException){e。printStackTrace()}} 最后,硬解码速度很快,效率很高,播放视频需要PTS时间戳处理。demo最好的办法就是让它渲染慢一点(demo视频文件是30fps,也就是说1000ms3033ms一帧yuv数据),所以在mediaCodec。releaseOutputBuffer(outIndex,true)休眠前(33ms)达到正常播放速度。