Android音视频-音频编解码

前面我们知道了采集音频,播放音频,保存音频数据,我们知道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整体思路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章