专栏首页曾大稳的博客MediaCodec硬编码pcm2aac

MediaCodec硬编码pcm2aac

MediaCodecAndroid(api>=16)提供的一个多媒体硬解编码库,能实现音视频的编解码。

工作原理:其内部有2个队列,一个是输入队列,一个是输出队列。输入队列负责存储编 解码前的原始数据存储,并输送给MediaCodec处理;输出队列负责存储编解码后 的新数据,可以直接处理或保存到文件中。

AAC 的头部信息介绍 :https://blog.csdn.net/jay100500/article/details/52955232


//mediacodec
   private MediaFormat encoderFormat = null;
   private MediaCodec encoder = null;
   private FileOutputStream outputStream = null;
   private MediaCodec.BufferInfo info = null;
   private int perpcmsize = 0;
   private byte[] outByteBuffer = null;
   private int aacsamplerate = 4;
   private double recordTime = 0;
   private int audioSamplerate = 0;

   private void initMediacodec(int samperate, File outfile)
   {
       try {
           aacsamplerate = getADTSsamplerate(samperate);
           //立体声
           encoderFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, samperate, 2);
           //96kbps fm音质
           encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
           encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
           encoderFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 4096);
           encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
           info = new MediaCodec.BufferInfo();
           if(encoder == null)
           {
               MyLog.d("craete encoder wrong");
               return;
           }
           recordTime = 0;
           encoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
           outputStream = new FileOutputStream(outfile);
           encoder.start();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

   private void encodecPcmToAAc(int size, byte[] buffer)
   {
       if(buffer != null && encoder != null)
       {
           //录音时间  size/ 采样率*声道数 * bits/8
           recordTime += size * 1.0 / (audioSamplerate * 2 * (16 / 8));
           MyLog.d("recordTime = " + recordTime);
           //回掉
           if(wlOnRecordTimeListener != null)
           {
               wlOnRecordTimeListener.onRecordTime((int) recordTime);
           }

           int inputBufferindex = encoder.dequeueInputBuffer(0);
           if(inputBufferindex >= 0)
           {
               ByteBuffer byteBuffer = encoder.getInputBuffers()[inputBufferindex];
               byteBuffer.clear();
               byteBuffer.put(buffer);
               encoder.queueInputBuffer(inputBufferindex, 0, size, 0, 0);
           }

           int index = encoder.dequeueOutputBuffer(info, 0);
           while(index >= 0)
           {
               try {
                   perpcmsize = info.size + 7;
                   outByteBuffer = new byte[perpcmsize];

                   ByteBuffer byteBuffer = encoder.getOutputBuffers()[index];
                   byteBuffer.position(info.offset);
                   byteBuffer.limit(info.offset + info.size);

                   addADtsHeader(outByteBuffer, perpcmsize, aacsamplerate);

                   byteBuffer.get(outByteBuffer, 7, info.size);
                   byteBuffer.position(info.offset);
                   outputStream.write(outByteBuffer, 0, perpcmsize);

                   encoder.releaseOutputBuffer(index, false);
                   index = encoder.dequeueOutputBuffer(info, 0);
                   outByteBuffer = null;
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }
   }

   private void addADtsHeader(byte[] packet, int packetLen, int samplerate)
   {
       int profile = 2; // AAC LC
       int freqIdx = samplerate; // samplerate
       int chanCfg = 2; // CPE

       packet[0] = (byte) 0xFF; // 0xFFF(12bit) 这里只取了8位,所以还差4位放到下一个里面
       packet[1] = (byte) 0xF9; // 第一个t位放F
       packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
       packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
       packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
       packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
       packet[6] = (byte) 0xFC;
   }

   private int getADTSsamplerate(int samplerate)
   {
       int rate = 4;
       switch (samplerate)
       {
           case 96000:
               rate = 0;
               break;
           case 88200:
               rate = 1;
               break;
           case 64000:
               rate = 2;
               break;
           case 48000:
               rate = 3;
               break;
           case 44100:
               rate = 4;
               break;
           case 32000:
               rate = 5;
               break;
           case 24000:
               rate = 6;
               break;
           case 22050:
               rate = 7;
               break;
           case 16000:
               rate = 8;
               break;
           case 12000:
               rate = 9;
               break;
           case 11025:
               rate = 10;
               break;
           case 8000:
               rate = 11;
               break;
           case 7350:
               rate = 12;
               break;
       }
       return rate;
   }

   private void releaseMedicacodec()
   {
       if(encoder == null)
       {
           return;
       }
       try {
           recordTime = 0;
           outputStream.close();
           outputStream = null;
           encoder.stop();
           encoder.release();
           encoder = null;
           encoderFormat = null;
           info = null;
           initmediacodec = false;

           MyLog.d("录制完成...");
       } catch (IOException e) {
           e.printStackTrace();
       }
       finally {
           if(outputStream != null)
           {
               try {
                   outputStream.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
               outputStream = null;
           }
       }
   }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android自定义GLSurfaceView

    当我们需要把同一个场景渲染到不同的Surface上时,此时系统GLSurfaceView 就不能满足需求了,所以我们需要自己创建EGL环境来实现渲染操作。 注意...

    曾大稳
  • c++基础语法

    注意:在开发过程中,cpp或者c会被编译为dll或者so供其调用者使用,一般把public的函数定义到h文件,不然调用者都不知道有哪些函数。

    曾大稳
  • HashMap源码分析

    HashMap是一个很经典的键值对集合,从它的广泛应用程度和源码的学习角度上我们不得不去解析它。 我们先看一下HashMap的存储结构((图片均来源于网络)),...

    曾大稳
  • codeforces 1203D1(暴力)

    给定两个字符串s和t,s中包含t的子串,求最大删除的区间的长度,使得t仍是s的子串

    dejavu1zz
  • codeforces 982C (dfs)

    Your task is to determine the maximum possible number of edges that can be remov...

    dejavu1zz
  • Android评分RationBar控件使用详解

    主要是不想用太多三方的控件,所以决定尽可能自己写,最近有写一个评分的页面,废话不多说直接上图

    砸漏
  • Go之heap

    今天来介绍下,golang的一个pkg包,containter/heap,官方实现的heap的操作,包 heap 为所有实现了 heap.Interface 的...

    灰子学技术
  • iOS开发UI之日期控件的使用(UIDatePicker)

    @property (nonatomic) UIDatePickerMode datePickerMode; 

    珲少
  • 在Laravel中使用MongoDB的方法示例

    在macOS中,MongoDB 扩展已经从Homebrew仓库中移除,需要通过pecl安装此扩展。

    砸漏
  • Android UI使用HorizontalListView实现水平滑动

    今天就介绍一个大神级人物自定义的ListView实现水平滑动,我知道要实现一个可以水平滑动的方法有很多,但是这个HorizontalListView用起来是真的...

    砸漏

扫码关注云+社区

领取腾讯云代金券