当前位置: 首页 > news >正文

电子网站风格设计郑州做网站的专业公司

电子网站风格设计,郑州做网站的专业公司,橘色网站模板,自己怎么设计logo制作前言 本篇博文主要讲述的是基于Android原生MediaCodec通过Camera2 API进行图像数据采集并编码为H.264的实现过程,如果对此感兴趣的不妨驻足观看,也欢迎大家大家对本文中描述不当或者不正确的地方进行指正。如果对于Camera2预览还不熟悉的可以观看博主上…

前言

本篇博文主要讲述的是基于Android原生MediaCodec通过Camera2 API进行图像数据采集并编码为H.264的实现过程,如果对此感兴趣的不妨驻足观看,也欢迎大家大家对本文中描述不当或者不正确的地方进行指正。如果对于Camera2预览还不熟悉的可以观看博主上一篇博文:Android 基于Camera2 API进行摄像机图像预览。

MediaCodec简介

MediaCodec 主要是用于对音视频进行编解码。它通常与 MediaExtractor、MediaMuxer、Surface 和 AudioTrack 等组件一起使用。MediaCodec 支持硬件加速,可以利用设备的硬件资源来提高编解码的性能。

1、MediaCodec 编解码流程

MediaCodec 采用异步方式处理数据,使用一组输入输出缓冲区(ByteBuffer)。编解码流程大致如下:

  • 请求一个空的输入缓冲区,填充满数据后传递给 MediaCodec 处理。

  • MediaCodec 处理完数据后,将结果输出至一个空的输出缓冲区中。

  • 从 MediaCodec 获取输出缓冲区的数据,消耗掉里面的数据后,释放回编解码器。

具体流程可以参考下图:

在这里插入图片描述

2、MediaCodec 生命周期

在这里插入图片描述

从上图可以看出MediaCodec 的生命周期包括三种状态:Stopped、Executing、Released。

  • Stopped,分三种子状态:

    • Configured,MediaCodec实例创建后,调用configure方法后就进入了Configured状态

    • Uninitialized,MediaCodec实例被创建后,在调用configure方法前都处于该状态;

    • Error,MediaCodec遇到错误时进入该状态,通常可能是队列操作返回错误或异常导致的;

  • Executing,分三种状态

    • Flushed,在调用start()方法后MediaCodec立即进入Flushed子状态,此时MediaCodec会拥有所有的缓存。可以在Executing状态的任何时候通过调用flush()方法返回到Flushed子状态;

    • Running,一旦第一个输入缓存(input buffer)被移出队列,MediaCodec就转入Running子状态,这种状态占据了MediaCodec的大部分生命周期。通过调用stop()方法转移到Uninitialized状态;

    • End of Stream,将一个带有end-of-stream标记的输入buffer入队列时,MediaCodec将转入End-of-Stream子状态。在这种状态下,MediaCodec不再接收之后的输入buffer,但它仍然产生输出buffer直到end-of-stream标记输出

  • Released,当使用完MediaCodec后,必须调用release()方法释放其资源。调用 release()方法进入最终的Released状态;

编码实现

老规矩,依然需要对MediaCodec进行封装,在封装之前我们需要先设计下对应的接口,而根据上面了解到的MediaCodec生命周期中的几个状态我们需要对其进行记录,方便对外获取,因此接口设计如下:

interface ICodec {enum class State{IDLE,START,STOP,CLOSE}fun start()fun stop()fun close()fun getState():State
}interface IVideoEncoder: ICodec 

这里的close对应的是MediaCodec的release接口。ICodec设计思想是考虑到后续会扩展出不止VideocEncoder还会有Decoder因此只包含了最原始的几个接口设计,针对Encoder和Decoder区别性的接口可以基于ICodec进行扩展继续添加,因为IVideoEncoder暂时没有需要额外添加的接口所以只是单纯的继承自ICodec,后面有需要再添加即可。

接着让我们再编写一个VideoCodec类并实现IVideoEncoder接口,开始我们的编码实现。

class VideoEncoder(private var params:VideoEncParams = VideoEncParams()):IVideoEncoder {private var state = ICodec.State.IDLEprivate var enccoder:MediaCodec? = nullprivate var inputSurface:Surface? = nullprivate var encodeThread:Thread? = nullprivate var callback:EncoderCallback? = nullfun setEncoderCallback(callback:EncoderCallback){this.callback = callback}interface EncoderCallback{fun onCallback(data:ByteArray,frameFlags:Int)}

VideoEncoder实现了IVideoEncoder接口并且构造时需要传入VideoEncParams,VideoEncParams我们等下再看,inputSurface是编码输入源,encodeThread用于异步进行编码,callback则是对外提供编码后数据的回调,回调并不仅仅只包含编码后的帧数据,还包含有一个Int类型的frameFlags,这个frameFlags可以理解为编码这一帧的类型,例如SPS帧或者关键帧等,现在我们回过头来看下传入的VideoEncParams:

class VideoEncParams(var mime:String = "video/avc",var codecWidth:Int = 1920,var codecHeight:Int = 1080,var bitRate:Int = 2048,var fps:Int = 30,var keyInterval:Float = 1f,var rotation:Int = 90
)

参数貌似有点多,但实际编码过程中的传参根据需要可能会更多,只是我这里罗列的这些参数已经满足我们当前示例的需要了,这些参数的意义等下在配置到编码器的时候我们再详细解释,继续往下。

    override fun start() {if(state == ICodec.State.START) returnstate =  ICodec.State.STARTif(enccoder == null){enccoder = MediaCodec.createEncoderByType(params.mime).apply {configure(createMediaFormat(),null,null,MediaCodec.CONFIGURE_FLAG_ENCODE)}inputSurface = enccoder?.createInputSurface()}enccoder?.start()encodeThread = Thread(encodeTask).apply { start() }}

这里我们实现了IVideoEncoder的第一个接口函数,也就是我们启动编码器的接口,函数的第一行代码做了一个保护,防止多次调用同一个编码器的start,如果全局变量enccoder为null就通过MediaCodec.createEncoderByType函数进行创建,创建时需要传入一个String类型的参数,这里我们传入的是params中保存的mime,上面我们也看到了这里的mime是“video/avc”。之后调用编码器configure函数进行了初始化配置,通过上面MediaCodec的生命周期可知,MediaCodec在编解码之前必须要先通过configure函数进行配置,才可以正常使用。

而这里的configure需要传入的参数有点多,我们一个一个看:

第一个参数类型是MediaFormat,createMediaFormat()函数的实际作用就是这里对VideoEncParams进行了转换将其转换成了MediaFormat对象。

第二个参数类型为Surface,是设置解码器的显示Surface我们这里用的是编码器,因此直接设置为null。

第三个参数类型为MediaCrypto,主要是与媒体数据加解密有关,我们这边不需要因此也设置为null。

第四个参数类型为int,设置为MediaCodec.CONFIGURE_FLAG_ENCODE配置MediaCodec为编码器。

现在我们来看下createMediaFormat()函数实现。

    private fun  createMediaFormat(): MediaFormat {var mediaFormat =  MediaFormat.createVideoFormat(params.mime,params.codecWidth,params.codecHeight)//设置比特率mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE,params.bitRate)//设置帧率mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,params.fps)//设置颜色模式mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)//设置关键帧频率 帧/秒mediaFormat.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL,params.keyInterval)//设置摄像机旋转角度mediaFormat.setInteger(MediaFormat.KEY_ROTATION,params.rotation)LogPrint.debug("createMediaFormat mediaFormat:$mediaFormat")return mediaFormat}

第一行代码是创建MediaFormat对象,MediaFormat可以理解为媒体数据的描述信息,通过createVideoFormat(),意思是创建一个与视频相关的描述信息对象,这里依然传入了mime,之后两个参数依次传入了需要编码视频的宽高。

MediaFormat设置媒体描述信息的方式是以键值对的方式传入的,键是字符串,值可以是int、long、float、String或ByteBuffer。

这里代码都有注释,就不再额外赘述。

让我们继续回到start()函数,当configure配置结束之后,通过enccoder获取到了一个Surface(inputSurface),该Surface可以理解为编码器的输入队列。

当编码器创建就绪之后,就创建了一个encodeThread线程,那么encodeTask的实现逻辑就是编码的关键逻辑了。

    private var encodeTask:Runnable = Runnable {//编码输出信息的对象,赋值由MediaCodec实现var bufferInfo = MediaCodec.BufferInfo()//当前可用的ByteBuffer索引var outputBufferIndex:Intvar encBuf:ByteArraytry {//死循环获取,也可以对MediaCodec设置callback获取while (state == ICodec.State.START) {//获取下一个可用的输出缓冲区,如果存在可用,返回值大于等于0,bufferInfo也会被赋值,最大等待时间是100000微秒,其实就是100毫秒outputBufferIndex = enccoder?.dequeueOutputBuffer(bufferInfo, 100000)!!//如果当前输出缓冲区没有可用的,返回负值,不同值含义不一样,有需要做判定即可if (outputBufferIndex < 0) {continue;}val outputBuffer: ByteBuffer = enccoder?.getOutputBuffer(outputBufferIndex)!!//调整数据位置,从offset开始。这样我们一会儿读取就不用传offset偏差值了。outputBuffer.position(bufferInfo.offset)//改完位置,那肯定要改极限位置吧,不然你数据不就少了数据末尾长度为offset的这一小部分?这两步不做也可以,get的时候传offset也一样outputBuffer.limit(bufferInfo.offset + bufferInfo.size)encBuf = ByteArray(bufferInfo.size)//获取编码数据outputBuffer.get(encBuf, 0, bufferInfo.size)callback?.onCallback(encBuf, bufferInfo.flags)//释放数据,不释放就一直在,MediaCodec数据满了可不行enccoder!!.releaseOutputBuffer(outputBufferIndex, false)}}catch (e:Exception){enccoder?.release()enccoder = nullinputSurface?.release()inputSurface = nullLog.e("VideoEncoder","VideoEncoder error:",e)}}

这块代码稍微有点多,我们来逐行看下,第一行代码是创建了一个BufferInfo对象,该对象主要是用来描述编码数据信息。第二行代码是当前MediaCodec输出buffer的索引位置,不理解的可以再回头看下上面给出的MediaCodec编解码流程图,第三行代码创建了一个ByteArray对象encBuf用于保存MediaCodec编码后的数据。

再往下就是核心的编码代码了,while循环中,通过MediaCodec的dequeueOutputBuffer获取当前可用的编码完成后的Buffer索引,第一个参数是上面我们创建的BufferInfo对象,第二个参数是等待时间,如果获取成功,MediaCodec会将缓存信息保存到BufferInfo对象中,并且返回Buffer的索引。

enccoder?.getOutputBuffer(outputBufferIndex)!!通过index获取输出Buffer。再之后是根据bufferInfo中的描述信息获取正确的缓存数据并保存到我们上面定义的encBuf中。

callback?.onCallback(encBuf, bufferInfo.flags)拿到编码数据之后通过该接口同步出去,这里这个代码可能有点不太合理,因为callback?.onCallback外部使用者如果使用不当可能会影响编码,理论上应该用队列同步出去会更好,这里我们先这样,后续我再进行优化。

while循环最后一行代码就是释放掉当前获取的输出Buffer数据,不释放的话MediaCodec缓存满了可能会出现异常。

再最后如果编码过程中出现异常,就会释放掉当前的编码器,按照生命周期其实通过MediaCodec.reset()也可以,当前这里为了方便,就直接释放了,等下次再start()重新创建即可。

至此编码就已经全部完成了,接下来让我们再加如一些其他的函数,使得VideoCodec更加完善。

    override fun stop() {if(state != ICodec.State.START) returnstate = ICodec.State.STOPencodeThread?.interrupt()enccoder?.stop()encodeThread = null}override fun close() {stop()state = ICodec.State.CLOSEenccoder?.release()inputSurface?.release()}override fun getState(): ICodec.State {return state}fun getInputSurface():Surface?{return inputSurface}

stop()和close()分别是停止解码器和释放解码器,stop()之后通过start()还能继续进行编码但是调用close()之后就不能再调用start(),否则就会报错,需要重新创建VideoEncodec进行编码。getState()没什么好说的,就只是返回当前编码器的状态,getInputSurface()将编码器的输入Surface(理解为队列)返回。

现在我们编码器就已经编写完成了,让我们看看如何跟我们的CameraWrapper进行组合编码Camera画面。让我们继续编写一个新的类CameraEncoder,通过这个类让我们把VideoEncodec和CameraWrapper组合起来,用以实现Camera画面编码为H.264。

class CameraEncoder :VideoEncoder.EncoderCallback{private var cameraWrapper: CameraWrapperprivate var videoEncoder:VideoEncoderprivate var callback:VideoEncoder.EncoderCallback? = nullconstructor(context:Context){cameraWrapper = CameraWrapper(context)videoEncoder = VideoEncoder()videoEncoder.setEncoderCallback(this)}fun start(surfaceView: SurfaceView){videoEncoder.start()cameraWrapper.setEncoderSurface(videoEncoder.getInputSurface()!!)cameraWrapper.startPreview(surfaceView)}fun stop(){videoEncoder.stop()cameraWrapper.stopPreview()cameraWrapper.setEncoderSurface(null)}fun close(){cameraWrapper.release()videoEncoder.close()}fun setEncoderCallback(callback:VideoEncoder.EncoderCallback){this.callback = callback}override fun onCallback(data: ByteArray,flags:Int) {callback?.onCallback(data,flags)}
}

因为代码量不多,而且没有什么难度,所以就直接全部贴出来了,在构造函数中同时创建了VideoCodec和CameraWrapper对象,对外的编码数据并不是通过VideoEncoder的回调直返回的,而是通过CameraEncoder的回调间接返回。

启动时先后启动了videoEncoder编码和cameraWrapper预览,特殊一点就是将videoEncoder中的输入Surface也就是我们上面说的可以理解为输入队列的哪个Surface传递到cameraWrapper这样在cameraWrapper预览时会自动关联起来。

停止时也是一样两个对象依次停止,不过在停止之后设置了cameraWrapper在启动时传入的Surface为空,其实也可以不用置空,但个人感觉这样可能会更好一点。

close()就不多说,与上面描述的start和stop逻辑一致。

至此我们Camera画面采集并编码为H.264就已经完成了。将编码后的数据保存到文件,然后通过VLC即可观看。

总结

Camera2与MediaCodec的结合在Android平台上提供了一种强大的视频处理解决方案。Camera2 API负责高效地从摄像头采集原始视频帧,而MediaCodec API则负责将这些帧实时编码为H.264格式,这是目前最广泛支持的视频编码标准之一。这种组合不仅利用了硬件加速来提高编码性能,减少CPU负担,还确保了视频的高质量输出和良好的兼容性。通过精确控制编码参数,可以根据应用需求调整视频的比特率、帧率和分辨率,实现定制化的视频录制和处理。总的来说,Camera2与MediaCodec的协同工作为开发者提供了一个灵活、高效的工具,用于创建和处理高质量的视频内容。

代码依然比较粗糙,但作为启蒙(用词貌似不当),应该是够了,后续随着博主的继续学习将会继续完善,感谢大家观看。


文章转载自:
http://dinncosplinter.bkqw.cn
http://dinncomonacan.bkqw.cn
http://dinncounperceptive.bkqw.cn
http://dinncoeuthanasia.bkqw.cn
http://dinncomtu.bkqw.cn
http://dinncocallithump.bkqw.cn
http://dinncoprovocatory.bkqw.cn
http://dinncounobtainable.bkqw.cn
http://dinncoiconically.bkqw.cn
http://dinncowitty.bkqw.cn
http://dinncoms.bkqw.cn
http://dinncolwop.bkqw.cn
http://dinncotelebus.bkqw.cn
http://dinncorankle.bkqw.cn
http://dinncounabsorbed.bkqw.cn
http://dinncosubcrystalline.bkqw.cn
http://dinncosuppress.bkqw.cn
http://dinncoconsecutive.bkqw.cn
http://dinncopcp.bkqw.cn
http://dinncoegoistically.bkqw.cn
http://dinncounbeautiful.bkqw.cn
http://dinncobluethroat.bkqw.cn
http://dinncodesignation.bkqw.cn
http://dinncohypermnesis.bkqw.cn
http://dinncosurjective.bkqw.cn
http://dinncouslta.bkqw.cn
http://dinncoregenerator.bkqw.cn
http://dinncoitaly.bkqw.cn
http://dinncomuntjac.bkqw.cn
http://dinncocutey.bkqw.cn
http://dinncowinepress.bkqw.cn
http://dinncodeterminately.bkqw.cn
http://dinncobenzosulphimide.bkqw.cn
http://dinncomanslayer.bkqw.cn
http://dinncoarabdom.bkqw.cn
http://dinncosimmer.bkqw.cn
http://dinncomfh.bkqw.cn
http://dinncoasgard.bkqw.cn
http://dinncooncoming.bkqw.cn
http://dinncowust.bkqw.cn
http://dinncodentalize.bkqw.cn
http://dinncolatifoliate.bkqw.cn
http://dinncodrest.bkqw.cn
http://dinncolockfast.bkqw.cn
http://dinncoenglishism.bkqw.cn
http://dinncobirdfarm.bkqw.cn
http://dinncothromboembolus.bkqw.cn
http://dinncomultidialectal.bkqw.cn
http://dinncodiplomacy.bkqw.cn
http://dinncoyea.bkqw.cn
http://dinncosophisticator.bkqw.cn
http://dinncoaftereffect.bkqw.cn
http://dinncoformalin.bkqw.cn
http://dinncoarnica.bkqw.cn
http://dinncokure.bkqw.cn
http://dinncolangostino.bkqw.cn
http://dinncogalumph.bkqw.cn
http://dinncoareopagitica.bkqw.cn
http://dinncoimpracticable.bkqw.cn
http://dinncobionic.bkqw.cn
http://dinncofungus.bkqw.cn
http://dinncophilomel.bkqw.cn
http://dinncoclomp.bkqw.cn
http://dinncoiiian.bkqw.cn
http://dinncostover.bkqw.cn
http://dinncostreamside.bkqw.cn
http://dinncoinquiline.bkqw.cn
http://dinncoprebiological.bkqw.cn
http://dinncopanoramist.bkqw.cn
http://dinncoignore.bkqw.cn
http://dinncoconsumption.bkqw.cn
http://dinncosnazzy.bkqw.cn
http://dinncomooncalf.bkqw.cn
http://dinncouprouse.bkqw.cn
http://dinncouniteable.bkqw.cn
http://dinncomerryman.bkqw.cn
http://dinncopastry.bkqw.cn
http://dinncoadviser.bkqw.cn
http://dinncoslender.bkqw.cn
http://dinncoigy.bkqw.cn
http://dinncocorporeity.bkqw.cn
http://dinncoisospondylous.bkqw.cn
http://dinncomacilent.bkqw.cn
http://dinncopolynia.bkqw.cn
http://dinncoaccessing.bkqw.cn
http://dinncopostglacial.bkqw.cn
http://dinncoamende.bkqw.cn
http://dinncophenolize.bkqw.cn
http://dinncoheterogeneity.bkqw.cn
http://dinncohistology.bkqw.cn
http://dinncomicrosome.bkqw.cn
http://dinncooscillatory.bkqw.cn
http://dinncologography.bkqw.cn
http://dinncodemotion.bkqw.cn
http://dinnconymphaeaceous.bkqw.cn
http://dinncounlicked.bkqw.cn
http://dinncojadish.bkqw.cn
http://dinncoindefinitely.bkqw.cn
http://dinncoregulator.bkqw.cn
http://dinncogustily.bkqw.cn
http://www.dinnco.com/news/3115.html

相关文章:

  • 网站制作公司电话一个完整的营销策划案范文
  • 兰州电商平台网站建设互联网广告投放
  • 企业网站开发报价单营销型网站建设运营
  • 怎样看出一个网站是那个公司做的优化关键词推广
  • 网站开发培训机构郑州做网站公司有哪些
  • 稀奇古怪好玩有用的网站seo批量建站
  • 网站开发进度缓慢短视频代运营方案模板
  • 广西造建设工程协会网站seo免费优化工具
  • 自己电脑上做网站别人访问关键词优化seo排名
  • 全国分类信息网站排名2022知名品牌营销案例100例
  • 园区官方网站建设个人网站制作源代码
  • 深圳做h5网站公司网址检测
  • 建设网站长沙湘潭网站制作
  • 设计公司做网站有用吗b2b商务平台
  • 做购物网站用什么应用个人网页怎么制作
  • 南昌专业网站建设百度企业推广怎么收费
  • 做桂林网站的图片百度网盘app下载安装 官方下载
  • 移动商城积分优化seo教程
  • 开网站的宣传图片怎么做独立站seo
  • 虚拟网站百度知道网页版入口
  • 汉阳网站建设公司3d建模培训学校哪家好
  • wordpress阿里云数据库seo+网站排名
  • 时时彩五星做号网站湖南seo快速排名
  • 个人做企业网站网站设计公司北京
  • 在互联网公司做网站长春网站搭建
  • 南京it外包公司搜索引擎优化seo课程总结
  • 教学平台网站开发第三波疫情将全面大爆发
  • 广州专业做网站公司seo专员工资待遇
  • python做后台开发移动网站杭州最好的seo公司
  • 建网站可行性分析seo关键词教程