Android MediaCodec解码AAC,AudioTrack播放PCM音频

   音频的编解码使用MediaCodec的方法是很相似的,由于之前做的项目是要实现全双工通信,所以在手机录音编码之后发送给IPC端的同时,还要解码来自IPC端的音频数据并播放,因此学习了AAC编解码。MediaCodec的编解码能力还是有限的,如果需要做专业的音视频编解码最好是选用ffempeg,功能齐全,支持的格式也比较多。还是先来学习一下MediaCodec这个安卓自带的编解码工具吧!

跟编码一样,要解码首先初始化解码器,告诉它你需要解码的音频格式,是否带ADT头,如果带了,头的格式和内容是什么

private MediaCodec audioDecoder;//音频解码器    
audioDecoder = MediaCodec.createDecoderByType("audio/mp4a-latm");

创建一个音频的解码器,初始化audioDecoder,具体的数据类型Type如下:

     * 
  • "video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm) *
  • "video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm) *
  • "video/avc" - H.264/AVC video *
  • "video/hevc" - H.265/HEVC video *
  • "video/mp4v-es" - MPEG4 video *
  • "video/3gpp" - H.263 video *
  • "audio/3gpp" - AMR narrowband audio *
  • "audio/amr-wb" - AMR wideband audio *
  • "audio/mpeg" - MPEG1/2 audio layer III *
  • "audio/mp4a-latm" - AAC audio (note, this is raw AAC packets, not packaged in LATM!) *
  • "audio/vorbis" - vorbis audio *
  • "audio/g711-alaw" - G.711 alaw audio *
  • "audio/g711-mlaw" - G.711 ulaw audio
  • 根据具体需求选择Type来创建解码器,本例子解码的是AAC音频数据所以写入"audio/mp4a-latm"。

    接下来是设置解码参数,使用MediaFormat来描述相关数据:

    MediaFormat mediaFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 16000, 1);
    //数据类型
    mediaFormat.setString(MediaFormat.KEY_MIME, mine);
     //采样率
    mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, KEY_SAMPLE_RATE);//16k
    //声道个数
    mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, KEY_CHANNEL_COUNT);//单声道
    mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
    mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024);//作用于inputBuffer的大小
    //比特率
    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 32000);         
    //用来标记aac的类型
    mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    

    这些参数根据音频参数来填写,接下来填写的是AAC的ADT头解码信息,这个东西没填对的话,解码会有问题,音频播出来是杂音。

    //用来标记AAC是否有adts头,1->有
    mediaFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1); 
    //ByteBuffer key
    byte[] data = new byte[]{(byte) 0x14, (byte) 0x08}; 
    ByteBuffer csd_0 = ByteBuffer.wrap(data); 
    //ADT头的解码信息
    mediaFormat.setByteBuffer("csd-0", csd_0);
    //解码器配置
    audioDecoder.configure(mediaFormat, null, null, 0);

    其中的data就是解码AAC的关键信息,该信息的格式:

    AAC Profile 5bits | 采样率 4bits | 声道数 4bits | 其他 3bits |

    AAC Main 0x01 
    AAC LC    0x02 
    AAC SSR  0x03

    采样率的参数为:

    0x00   96000 
    0x01   88200 
    0x02   64000 
    0x03   48000 
    0x04   44100
    0x05   32000
    0x06   24000 
    0x07   22050 
    0x08   16000 
    0x09   12000 
    0x0A   11025 
    0x0B    8000 
    0x0C   reserved 
    0x0D   reserved 
    0x0E   reserved 
    0x0F   escape value

    声道数:

    0x00 - defined in audioDecderSpecificConfig 
    0x01 单声道(center front speaker) 
    0x02 双声道(left, right front speakers) 
    0x03 三声道(center, left, right front speakers) 
    0x04 四声道(center, left, right front speakers, rear surround speakers) 
    0x05 五声道(center, left, right front speakers, left surround, right surround rear speakers) 
    0x06 5.1声道(center, left, right front speakers, left surround, right surround rear speakers, front low frequency effects speaker) 
    0x07 7.1声道(center, left, right center front speakers, left, right outside front speakers, left surround, right surround rear speakers, front low frequency effects speaker) 
    0x08-0x0F - reserved

    我的音频数据是  AAC-LC ,16000,单声道  ,参数分别是:0X02 0X08 0X01 0X00  取参数的后面两位,根据信息格式所占bit,换成二进制为:00010 1000 0001 000

    Android MediaCodec解码AAC,AudioTrack播放PCM音频_第1张图片

    将数字输入之后得到1408这个参数,再用两个byte来存放所以是0x14,0x08.

    同理,如果是8K的采样率则换成:data = new byte[]{(byte) 0x15, (byte) 0x88};

    至此,音频的解码参数已经设置完毕,接下来就是开始解码了!

    /**
         * aac音频解码并播放PCM音频
         */
        public void decode(byte[] buf, int length, AudioTrack audioTrack) {
            //输入ByteBuffer
            ByteBuffer[] codecInputBuffers = audioDecoder.getInputBuffers();
            //输出ByteBuffer
            ByteBuffer[] codecOutputBuffers = audioDecoder.getOutputBuffers();
            //等待时间
            long kTimeOutUs = 1000;
            try {
                //返回一个包含有效数据的input buffer的index,-1->不存在
                int inputBufIndex = audioDecoder.dequeueInputBuffer(kTimeOutUs);
                if (inputBufIndex >= 0) {
                    //获取当前的ByteBuffer
                    ByteBuffer currentBuf = codecInputBuffers[inputBufIndex];
                    currentBuf.clear();
                    currentBuf.put(buf, 0, length);
                    //将指定index的input buffer提交给解码器
                    audioDecoder.queueInputBuffer(inputBufIndex, 0, length, getPTSUs(), 0);
                }
                //编解码器缓冲区
                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                int outputBufferIndex = audioDecoder.dequeueOutputBuffer(info, kTimeOutUs);
                ByteBuffer outputBuffer;
                while (outputBufferIndex >= 0) {
                    //获取解码后的ByteBuffer
                    outputBuffer = codecOutputBuffers[outputBufferIndex];
                    //用来保存解码后的数据
                    byte[] outData = new byte[info.size];
                    outputBuffer.get(outData);
                    //清空缓存
                    outputBuffer.clear();
             
                    //播放解码后的数据
                    audioTrack.write(outData, 0, outData.length);
                    //释放已经解码的buffer
                    audioDecoder.releaseOutputBuffer(outputBufferIndex, false);
                    outputBufferIndex = audioDecoder.dequeueOutputBuffer(info, kTimeOutUs);
                }
            } catch (Exception e) {
                Log.e(TAG, e.toString());
                e.printStackTrace();
            }
        }
    
    
    	 /**
         * 获取当前的时间戳
         *
         * @return
         */
        private long getPTSUs() {
            long result = System.nanoTime() / 1000;
            if (result < prevPresentationTimes) {
                result = (prevPresentationTimes - result) + result;
            }
            return result;
        }点击打开链接

    将解码完毕的PCM数据直接放进audioTrack中就可以播放了,但是在这之前要提前初始化好audioTrack。

    private static final int AUDIO_BUF_SIZE = 1024;
        private int SAMPLE_RATE = 16000; //采样率8K或16k
        private AudioTrack audioTrack;
        private int buffsize = 0;
    
    	buffsize = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
            
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,//播放途径  外放
                            SAMPLE_RATE,
                            AudioFormat.CHANNEL_OUT_MONO,
                            AudioFormat.ENCODING_PCM_16BIT,
                            buffsize * 4,
                            AudioTrack.MODE_STREAM);
              
        //启动AudioTrack
        audioTrack.play();

    这里是我的Demo地址: https://download.csdn.net/download/lavender1626/10438185

    Demo中,下载后,请自己将AAC音频放入raw文件夹中,每次读取一帧的数据,inputStream.read(audioBuffer,0,size),size为一帧的大小,并根据自己的音频文件修改音频参数。

    

    

    你可能感兴趣的:(android编解码,MediaCodec解码AAC,AudioTrack播放PCM)