Android音视频-音频编解码

来源:互联网 发布:ketchup 2017 for mac 编辑:程序博客网 时间:2024/05/16 17:35

前面我们知道了采集音频,播放音频,保存音频数据,我们知道PCM纯音频数据没有经过压缩编码处理的数据是很大的。很有必要了解编解码来处理这个问题。

简介

音视频的编码方式分为两种。

  • 硬编码:
    用设备GPU去实现编解码,这样可以减轻CPU的压力。

  • 软编码:
    让CPU来进行编解码,在c层代码来进行编解码,因为c/c++有很多好的编解码库。

  • 软硬编码对比:
    硬编的好处主要在于速度快,而且系统自带不需要引入外部的库,但是特性支持有限,而且硬编的压缩率一般偏低,而对于软编码来说,虽然速度较慢,但是压缩率比较高,而且支持的H264特性也会比硬编码多很多,相对来说比较可控。就可用性而言,在4.4+的系统上,MediaCodec的可用性是能够基本保证的,但是不同等级的机器的编码器能力会有不少差别,建议可以根据机器的配置,选择不同的编码器配置。

在Android 4.1之前没有提供硬编解码的API,所以基本都是采用开源的那些库,比如著名的FFMpeg实现软编解码。但是通常情况下,同一平台同一硬件环境,硬编码的速度快于软件编码,软编码使用CPU来进行计算,会消耗一些app的运算效率。在Android4.1出来了一个新的API:MediaCodec可以支持硬编解码。MediaCodec可以支持对音频和视频的编解码,我们就要学会使用它来进行音频的编解码操作。

MediaCodec

简介

对于API的介绍我们的第一反应,看官网
还发现了一个哥们翻译的中文版本
总结来说,MediaCodec它是官方提供的硬编码API,首先对他进行参数的配置,然后把数据扔给它,它在内部完成编码或者解码的工作,然后把处理好的数据输出给我们。
贴一张官网的处理图片:


这里写图片描述

解析:
MediaCodec采用了两个缓冲区队列,异步处理数据。

  • 客户端从input缓冲区队列申请empty buffer(调用dequeueInputBuffer方法)
  • 客户端将要编解码的数据拷贝到empty buffer,然后放入input缓冲区队列(调用queueInputBuffer方法)
  • MediaCodec内部从input缓冲区队列取出一帧数据进行编解码处理
  • 处理结束后将原始数据buffer置为empty再放回input缓冲区队列,将编解码的数据放入到output缓冲区队列
  • 客户端从output缓冲区队列申请编解码的buffer(调用dequeueoutputBuffer方法)
  • 客户端对编解码后的buffer数据进行渲染或者播放
  • 客户端处理完上面的步骤后再将该buffer放回到output缓冲区队列(调用releaseOutputBuffer)

MediaCodec使用流程

  • 创建MediaCodec(调用createDecoderByType方法)
  • 配置MediaCodec(调用configure方法)
  • 开始编解码(调用start方法)
  • 循环数据输入输出
    • dequeueInputBuffer
    • queueInputBuffer
    • dequeueOutputBuffer
    • releaseOutputBuffer
  • 停止编解码(调用stop方法)
  • 释放资源(调用release方法)

MediaCodec使用代码

摘自官网代码示例

  • 同步使用
MediaCodec codec = MediaCodec.createByCodecName(name); codec.configure(format, …); MediaFormat outputFormat = codec.getOutputFormat(); // option B codec.start(); for (;;) {   int inputBufferId = codec.dequeueInputBuffer(timeoutUs);   if (inputBufferId >= 0) {     ByteBuffer inputBuffer = codec.getInputBuffer(…);     // fill inputBuffer with valid data     …     codec.queueInputBuffer(inputBufferId, …);   }   int outputBufferId = codec.dequeueOutputBuffer(…);   if (outputBufferId >= 0) {     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A     // bufferFormat is identical to outputFormat     // outputBuffer is ready to be processed or rendered.     …     codec.releaseOutputBuffer(outputBufferId, …);   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {     // Subsequent data will conform to new format.     // Can ignore if using getOutputFormat(outputBufferId)     outputFormat = codec.getOutputFormat(); // option B   } } codec.stop(); codec.release();
  • 异步使用
MediaCodec codec = MediaCodec.createByCodecName(name); MediaFormat mOutputFormat; // member variable codec.setCallback(new MediaCodec.Callback() {   @Override   void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);     // fill inputBuffer with valid data     …     codec.queueInputBuffer(inputBufferId, …);   }   @Override   void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A     // bufferFormat is equivalent to mOutputFormat     // outputBuffer is ready to be processed or rendered.     …     codec.releaseOutputBuffer(outputBufferId, …);   }   @Override   void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {     // Subsequent data will conform to new format.     // Can ignore if using getOutputFormat(outputBufferId)     mOutputFormat = format; // option B   }   @Override   void onError(…) {     …   } }); codec.configure(format, …); mOutputFormat = codec.getOutputFormat(); // option B codec.start(); // wait for processing to complete codec.stop(); codec.release();
  • Android 5.0以下使用
MediaCodec codec = MediaCodec.createByCodecName(name); codec.configure(format, …); codec.start();//API的区别在这里 ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); for (;;) {   int inputBufferId = codec.dequeueInputBuffer(…);   if (inputBufferId >= 0) {     // fill inputBuffers[inputBufferId] with valid data     …     codec.queueInputBuffer(inputBufferId, …);   }   int outputBufferId = codec.dequeueOutputBuffer(…);   if (outputBufferId >= 0) {     // outputBuffers[outputBufferId] is ready to be processed or rendered.     …     codec.releaseOutputBuffer(outputBufferId, …);   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {     outputBuffers = codec.getOutputBuffers();   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {     // Subsequent data will conform to new format.     MediaFormat format = codec.getOutputFormat();   } } codec.stop(); codec.release();

我们实现一个实时的音频录制播放的demo,查看

参考资料:
可以查看MediaCodec的使用小demo程序
demo整体思路

原创粉丝点击