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

网站站长统计怎么弄深圳高端网站制作公司排名

网站站长统计怎么弄,深圳高端网站制作公司排名,flash网站源码免费下载,徐家汇做网站前言 本篇博文主要讲述的是基于Android原生MediaCodec通过Camera2 API进行图像数据采集并编码为H.264的实现过程#xff0c;如果对此感兴趣的不妨驻足观看#xff0c;也欢迎大家大家对本文中描述不当或者不正确的地方进行指正。如果对于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分三种子状态 ConfiguredMediaCodec实例创建后调用configure方法后就进入了Configured状态 UninitializedMediaCodec实例被创建后在调用configure方法前都处于该状态 ErrorMediaCodec遇到错误时进入该状态通常可能是队列操作返回错误或异常导致的 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接口并且构造时需要传入VideoEncParamsVideoEncParams我们等下再看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获取到了一个SurfaceinputSurface,该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) {//获取下一个可用的输出缓冲区如果存在可用返回值大于等于0bufferInfo也会被赋值最大等待时间是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://www.tj-hxxt.cn/news/221694.html

相关文章:

  • 中国做爰网站学校官网网页设计
  • 北京做网站商标的公司个人做网站有什么条件
  • 公司网站怎么做网站备案烟台网站开发
  • 专业建站流程安防网站模板
  • 做网站珊瑚橙颜色怎么搭配好看网站建设设计培训班
  • 网站风格一般具有哪三大特征深圳招聘官网
  • 想接网站自己做陕西省住房建设厅官网
  • 望都网站建设域名查询网中国万网
  • 四川网站建设和优化做网站 怎么样找客户
  • dede网站栏目管理如何建设wordpress 新标签打开
  • 企业网站的开发公司wordpress设计模板
  • 建材网站建设公司网站首页没有权重
  • 做阿里网站的分录wordpress的安装步骤
  • 国内好的设计网站国外外贸平台
  • 56做视频网站淘宝客网站一般用什么做的
  • 网站开发外包公司坑怎么做优惠券的网站
  • 提供资料下载的网站如何建设项目网站开发
  • 正规网站建设制作用仿站工具做网站
  • 网站开发后 怎么换前端正规电商培训班
  • 建网站需要数据库吗标准网站建设报价
  • 网站建设价格标准新闻做网站什么空间好
  • 专业房地产网站建设做音乐网站的目地
  • 用织梦做的手机网站怎么才能和电脑同步怎么给网站绑定域名
  • 河北省电力建设第二工程公司网站wordpress织梦
  • 网站 备案 几天购物网站后台模板
  • 自己做的网站怎么连接计算机怎么注册网络域名
  • 网站建设期任务及总结西安官网seo方法
  • 网站开发建设合同书做信誉认证对网站有什么好处
  • 教育网站开发价钱建设一个广告联盟的网站
  • 成都网站建设电话wordpress备份数据库结构