利用MediaCodec对音频编解码

来源:互联网 发布:iptables 禁用端口 编辑:程序博客网 时间:2024/06/06 00:34

刚开始工作,之前学习的全是android一些比较肤浅的对象,也都是利用现成的组件完成一些自己看起来比较美的界面,然而一工作,接触的就是自己不会的东西,老板让把音频解码成原始数据,也就是PCM格式的数据再通过socket传输,经过一番痛苦的查阅资料后,发现android内部还是有音视频解码器的,估计那些直播也就是用的这方面的技术。现在和大家分享一下如何把MP3,AAC格式的音频解码。

参考自:http://blog.csdn.net/TinsanMr/article/details/51049179

public class AudioCodec {        private static final String TAG = "AudioCodec";      private String encodeType;      private String srcPath;      private String dstPath;      private MediaCodec mediaDecode;      private MediaCodec mediaEncode;      private MediaExtractor mediaExtractor;      private ByteBuffer[] decodeInputBuffers;      private ByteBuffer[] decodeOutputBuffers;      private ByteBuffer[] encodeInputBuffers;      private ByteBuffer[] encodeOutputBuffers;      private MediaCodec.BufferInfo decodeBufferInfo;      private MediaCodec.BufferInfo encodeBufferInfo;      private FileOutputStream fos;      private BufferedOutputStream bos;      private FileInputStream fis;      private BufferedInputStream bis;      private ArrayList<byte[]> chunkPCMDataContainer;//PCM数据块容器      private OnCompleteListener onCompleteListener;      private OnProgressListener onProgressListener;      private long fileTotalSize;      private long decodeSize;      private static AudioCodec mAudioCodec;      public static AudioCodec getInstance() {          if (mAudioCodec == null) {mAudioCodec = new AudioCodec();}return mAudioCodec;  //单例模式    }        /**      * 设置编码器类型      * @param encodeType      */      public void setEncodeType(String encodeType) {          this.encodeType=encodeType;      }        /**      * 设置输入输出文件位置      * @param srcPath      * @param dstPath      */      public void setIOPath(String srcPath, String dstPath) {          this.srcPath=srcPath;          this.dstPath=dstPath;      }        /**      * 此类已经过封装      * 调用prepare方法 会初始化Decode 、Encode 、输入输出流 等一些列操作      */      public void prepare() {            if (encodeType == null) {              throw new IllegalArgumentException("encodeType can't be null");          }            if (srcPath == null) {              throw new IllegalArgumentException("srcPath can't be null");          }            if (dstPath == null) {              throw new IllegalArgumentException("dstPath can't be null");          }            try {              fos = new FileOutputStream(new File(dstPath));              bos = new BufferedOutputStream(fos,200*1024);              File file = new File(srcPath);              fileTotalSize=file.length();          } catch (IOException e) {              e.printStackTrace();          }          chunkPCMDataContainer= new ArrayList<>();          initMediaDecode();//解码器            if (encodeType == MediaFormat.MIMETYPE_AUDIO_AAC) {              initAACMediaEncode();//AAC编码器          }else if (encodeType == MediaFormat.MIMETYPE_AUDIO_MPEG) {              initMPEGMediaEncode();//mp3编码器          }        }        /**      * 初始化解码器      */      private void initMediaDecode() {          try {              mediaExtractor=new MediaExtractor();//此类可分离视频文件的音轨和视频轨道              mediaExtractor.setDataSource(srcPath);//媒体文件的位置              for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道 此处我们传入的是音频文件,所以也就只有一条轨道                  MediaFormat format = mediaExtractor.getTrackFormat(i);                  String mime = format.getString(MediaFormat.KEY_MIME);                  if (mime.startsWith("audio")) {//获取音频轨道  //                    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 * 1024);                      mediaExtractor.selectTrack(i);//选择此音频轨道                      mediaDecode = MediaCodec.createDecoderByType(mime);//创建Decode解码器                      mediaDecode.configure(format, null, null, 0);                      break;                  }              }          } catch (IOException e) {              e.printStackTrace();          }            if (mediaDecode == null) {              Log.e(TAG, "create mediaDecode failed");              return;          }          mediaDecode.start();//启动MediaCodec ,等待传入数据          decodeInputBuffers=mediaDecode.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据          decodeOutputBuffers=mediaDecode.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据          decodeBufferInfo=new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息          showLog("buffers:" + decodeInputBuffers.length);      }          /**      * 初始化AAC编码器      */      private void initAACMediaEncode() {          try {              MediaFormat encodeFormat = MediaFormat.createAudioFormat(encodeType, 44100, 2);//参数对应-> mime type、采样率、声道数              encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率              encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);              encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024);              mediaEncode = MediaCodec.createEncoderByType(encodeType);              mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);          } catch (IOException e) {              e.printStackTrace();          }            if (mediaEncode == null) {              Log.e(TAG, "create mediaEncode failed");              return;          }          mediaEncode.start();          encodeInputBuffers=mediaEncode.getInputBuffers();          encodeOutputBuffers=mediaEncode.getOutputBuffers();          encodeBufferInfo=new MediaCodec.BufferInfo();      }        /**      * 初始化MPEG编码器      */      private void initMPEGMediaEncode() {                }        private boolean codeOver = false;      /**      * 开始转码      * 音频数据{@link #srcPath}先解码成PCM  PCM数据在编码成想要得到的{@link #encodeType}音频格式      * mp3->PCM->aac      */      public void startAsync() {          showLog("start");            new Thread(new DecodeRunnable()).start();          new Thread(new EncodeRunnable()).start();        }        /**      * 将PCM数据存入{@link #chunkPCMDataContainer}      * @param pcmChunk PCM数据块      */      private void putPCMData(byte[] pcmChunk) {          synchronized (AudioCodec.class) {//记得加锁              chunkPCMDataContainer.add(pcmChunk);          }      }        /**      * 在Container中{@link #chunkPCMDataContainer}取出PCM数据      * @return PCM数据块      */      private byte[] getPCMData() {          synchronized (AudioCodec.class) {//记得加锁              showLog("getPCM:"+chunkPCMDataContainer.size());              if (chunkPCMDataContainer.isEmpty()) {                  return null;              }                byte[] pcmChunk = chunkPCMDataContainer.get(0);//每次取出index 0 的数据              chunkPCMDataContainer.remove(pcmChunk);//取出后将此数据remove掉 既能保证PCM数据块的取出顺序 又能及时释放内存              return pcmChunk;          }      }          /**      * 解码{@link #srcPath}音频文件 得到PCM数据块      * @return 是否解码完所有数据      */      private void srcAudioFormatToPCM() {          for (int i = 0; i < decodeInputBuffers.length-1; i++) {          int inputIndex = mediaDecode.dequeueInputBuffer(-1);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧          if (inputIndex < 0) {              codeOver =true;              return;          }            ByteBuffer inputBuffer = decodeInputBuffers[inputIndex];//拿到inputBuffer          inputBuffer.clear();//清空之前传入inputBuffer内的数据          int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中          if (sampleSize <0) {//小于0 代表所有数据已读取完成                  codeOver=true;              }else {                  mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);//通知MediaDecode解码刚刚传入的数据                  mediaExtractor.advance();//MediaExtractor移动到下一取样处                  decodeSize+=sampleSize;              }          }            //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒          //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待          int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);    //        showLog("decodeOutIndex:" + outputIndex);          ByteBuffer outputBuffer;          byte[] chunkPCM;          while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据              outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer              chunkPCM = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小              outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中              outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据              putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码              mediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据              outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束          }        }        /**      * 编码PCM数据 得到{@link #encodeType}格式的音频文件,并保存到{@link #dstPath}      */      private void dstAudioFormatFromPCM() {            int inputIndex;          ByteBuffer inputBuffer;          int outputIndex;          ByteBuffer outputBuffer;          byte[] chunkAudio;          int outBitSize;          int outPacketSize;          byte[] chunkPCM;    //        showLog("doEncode");          for (int i = 0; i < encodeInputBuffers.length-1; i++) {              chunkPCM=getPCMData();//获取解码器所在线程输出的数据 代码后边会贴上              if (chunkPCM == null) {                  break;              }              inputIndex = mediaEncode.dequeueInputBuffer(-1);//同解码器              inputBuffer = encodeInputBuffers[inputIndex];//同解码器              inputBuffer.clear();//同解码器              inputBuffer.limit(chunkPCM.length);              inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer              mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码          }                outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);//同解码器              while (outputIndex >= 0) {//同解码器                    outBitSize=encodeBufferInfo.size;                  outPacketSize=outBitSize+7;//7为ADTS头部的大小                  outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer                  outputBuffer.position(encodeBufferInfo.offset);                  outputBuffer.limit(encodeBufferInfo.offset + outBitSize);                  chunkAudio = new byte[outPacketSize];                  addADTStoPacket(chunkAudio,outPacketSize);//添加ADTS 代码后面会贴上                  outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中 偏移量offset=7 你懂得                  outputBuffer.position(encodeBufferInfo.offset);  //                showLog("outPacketSize:" + outPacketSize + " encodeOutBufferRemain:" + outputBuffer.remaining());                  try {                      bos.write(chunkAudio,0,chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.aac                  } catch (IOException e) {                      e.printStackTrace();                  }                    mediaEncode.releaseOutputBuffer(outputIndex,false);                  outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 10000);                }      }        /**      * 添加ADTS头      * @param packet      * @param packetLen      */      private void addADTStoPacket(byte[] packet, int packetLen) {          int profile = 2; // AAC LC          int freqIdx = 4; // 44.1KHz          int chanCfg = 2; // CPE      // fill in ADTS data          packet[0] = (byte) 0xFF;          packet[1] = (byte) 0xF9;          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;      }        /**      * 释放资源      */      public void release() {          try {              if (bos != null) {                  bos.flush();              }          } catch (IOException e) {              e.printStackTrace();          }finally {              if (bos != null) {                  try {                      bos.close();                  } catch (IOException e) {                      e.printStackTrace();                  }finally {                      bos=null;                  }              }          }            try {              if (fos != null) {                  fos.close();              }          } catch (IOException e) {              e.printStackTrace();          }finally {              fos=null;          }            if (mediaEncode != null) {              mediaEncode.stop();              mediaEncode.release();              mediaEncode=null;          }            if (mediaDecode != null) {              mediaDecode.stop();              mediaDecode.release();              mediaDecode=null;          }            if (mediaExtractor != null) {              mediaExtractor.release();              mediaExtractor=null;          }            if (onCompleteListener != null) {              onCompleteListener=null;          }            if (onProgressListener != null) {              onProgressListener=null;          }          showLog("release");      }        /**      * 解码线程      */      private class DecodeRunnable implements Runnable{            @Override          public void run() {              while (!codeOver) {                  srcAudioFormatToPCM();              }          }      }        /**      * 编码线程      */      private class EncodeRunnable implements Runnable {            @Override          public void run() {              long t=System.currentTimeMillis();              while (!codeOver || !chunkPCMDataContainer.isEmpty()) {                  dstAudioFormatFromPCM();              }              if (onCompleteListener != null) {                  onCompleteListener.completed();              }              showLog("size:"+fileTotalSize+" decodeSize:"+decodeSize+"time:"+(System.currentTimeMillis()-t));          }      }          /**      * 转码完成回调接口      */      public interface OnCompleteListener{          void completed();      }        /**      * 转码进度监听器      */      public interface OnProgressListener{          void progress();      }        /**      * 设置转码完成监听器      * @param onCompleteListener      */      public void setOnCompleteListener(OnCompleteListener onCompleteListener) {          this.onCompleteListener=onCompleteListener;      }        public void setOnProgressListener(OnProgressListener onProgressListener) {          this.onProgressListener = onProgressListener;      }        private void showLog(String msg) {          Log.e("AudioCodec", msg);      }  }  

上面这个工具类适用于android 4.0,而对于android 5.0以上的系统,从API 21开始就弃用了getInputBuffers()和getOutputBuffers()两个方法,因为列中移除位置和输出缓冲区的限制将被设置为有效数据范围,所以不要使用这种方法,而选择用getInputBuffer(int index)和getOutputBuffer(int index)。
对于这两个方法,官方API有详细解释。



都是API 21新增加的方法,解释也都差不多,返回一个清除过的ByteBuffer缓冲区对象,不过getInputBuffer()是可写的,getOutputBuffer()是只读的,任何ByteBuffer或Image之前返回的对象都必须不再被使用。
参数index就是dequeueInputBuffer(方式时得到的index。
所以在android 5.0时,上述类中的initMediaDecode()方法需要改一下:
private void initMediaDecode() {try {mediaExtractor = new MediaExtractor();// 此类可分离视频文件的音轨和视频轨道mediaExtractor.setDataSource(srcPath);// 媒体文件的位置for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {// 遍历媒体轨道// 此处我们传入的是音频文件,所以也就只有一条轨道MediaFormat format = mediaExtractor.getTrackFormat(i);String mime = format.getString(MediaFormat.KEY_MIME);if (mime.startsWith("audio")) {// 获取音频轨道// format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 200 *// 1024);mediaExtractor.selectTrack(i);// 选择此音频轨道mediaDecode = MediaCodec.createDecoderByType(mime);// 创建Decode解码器long)和dequeueOutputBuffer(MediaCodec.BufferInfo,long)返回的值,或者使用异步mediaDecode.configure(format, null, null, 0);break;}}} catch (IOException e) {e.printStackTrace();}if (mediaDecode == null) {Log.e(TAG, "create mediaDecode failed");return;}mediaDecode.start();// 启动MediaCodec ,等待传入数据decodeBufferInfo = new MediaCodec.BufferInfo();// 用于描述解码得到的byte[]数据的相关信息}

srcAudioFormatToPCM()方法也要改一下:
private void srcAudioFormatToPCM() {for (int i = 0; i < 4; i++) {//这个4就是原来dequeueInputBuffers的长度-1,打印多首歌都为4,有待考证inputIndex = mediaDecode.dequeueInputBuffer(-1);// -1代表一直等待,0表示不等待// 建议-1,避免丢帧if (inputIndex < 0) {codeOver = true;return;}ByteBuffer inputBuffer = mediaDecode.getInputBuffer(inputIndex);// 拿到inputBufferinputBuffer.clear();// 清空之前传入inputBuffer内的数据int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);// MediaExtractor读取数据到inputBuffer中if (sampleSize < 0) {// 小于0 代表所有数据已读取完成codeOver = true;} else {mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0);// 通知MediaDecode解码刚刚传入的数据mediaExtractor.advance();// MediaExtractor移动到下一取样处decodeSize += sampleSize;}}outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 10000);// showLog("decodeOutIndex:" + outputIndex);ByteBuffer outputBuffer;byte[] chunkPCM;while (outputIndex >= 0) {// 每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据outputBuffer = mediaDecode.getOutputBuffer(outputIndex);// 拿到用于存放PCM数据的BufferchunkPCM = new byte[decodeBufferInfo.size];// BufferInfo内定义了此数据块的大小outputBuffer.get(chunkPCM);// 将Buffer内的数据取出到字节数组中outputBuffer.clear();// 数据取出后一定记得清空此Buffer// MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据putPCMData(chunkPCM);// 自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码try {pcmBos.write(chunkPCM); // 存放PCM数据} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}mediaDecode.releaseOutputBuffer(outputIndex, false);// 此操作一定要做,不然MediaCodec用完所有的Buffer后// 将不能向外输出数据outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo,10000);// 再次获取数据,如果没有数据输出则outputIndex=-1 循环结束}}
参考自上面那个大神的blog的方法写出来的其实是解码再转码,如果只需要得到解码数据的话就只单独执行解码线程,然后在srcAudioFormatToPCM()方法中的while循环里将chunkPCM以文件流的形式保存即可。如果有上面不明白的地方,希望大家指出


0 0
原创粉丝点击