流程大致分为三部分:1、获取从麦克风得到的pcm数据;2、对拿到的pcm数据进行硬编码;3、把编码后的数据进行处理;

一、获取从麦克风得到的pcm数据

1、使用AudioRecord 进行音频数据获取时,初始化AudioRecord之后,调用startRecording()方法进行开始录音;

/**
     * 第一步初始化音频采集
     */
    public void initAudioRecord() {


        //获取最小音频buffer,给音频设置缓存区不能小于这个大小
        int min_buffer_size = AudioRecord.getMinBufferSize(Constants.AUDIO_CHANNEL, Constants.AUDIO_SAMPLERATE,
                AudioFormat.ENCODING_PCM_16BIT);


        //用来获取最终的缓存区大小 SAMPLES_PER_FRAME 每帧的采样率 FRAMES_PER_BUFFER 每次缓存的帧数
        int buffer_size = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER;
        if (buffer_size < min_buffer_size)
            buffer_size = ((min_buffer_size / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2;// TODO: 18-3-29  未理解为何这里要乘2?


        //用来缓存音频数据的buffer
        inBuffer = new byte[SAMPLES_PER_FRAME];


        //根据音频的来源进行初始化:麦克风,或者其他
        for (final int source : AUDIO_SOURCES) {
            try {
                //初始化AudioRecord,音频来源,采样率,声道,返回的音频格式,缓存区的大小
                audioRecord = new AudioRecord(source, this.sampleRate, this.nInputChannels,
                        AudioFormat.ENCODING_PCM_16BIT, buffer_size);
                if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED)
                    audioRecord = null;
            } catch (final Exception e) {
                audioRecord = null;
            }
            if (audioRecord != null) break;
        }
    }



2、使用for循环进行 pcm数据的获取;

/**
     * 开始对采集到的音频进行硬编码
     */
    public void startEncodec() {
        //开始进行
        if (audioRecord != null && audioRecord.getState() != AudioRecord.STATE_UNINITIALIZED) {
            audioRecord.startRecording();//开始进行录音


            while (true && !isStop) {
                try {
                    Arrays.fill(inBuffer, (byte) 0);//把原来的数据清空
                    int err = audioRecord.read(inBuffer, 0, inBuffer.length);//开始从audioRecord中读pcm输出出来
                    if (err <= 0) {
                        if (err == AudioRecord.ERROR_INVALID_OPERATION) {
                            MyLog.e("OF", "AudioRecord error ERROR_INVALID_OPERATION");
                            return;
                        }
                        if (err == AudioRecord.ERROR_BAD_VALUE) {
                            MyLog.e("OF", "AudioRecord error ERROR_BAD_VALUE");
                            return;
                        }
                        if (err == 0) {
                            MyLog.e("OF", "AudioRecord error 0 samples read");
                            return;
                        }
                    } else {
                        hardEncoder(inBuffer, inBuffer.length);
                    }


                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }



二、pcm数据进行硬编码

        硬编码的解释已经有很多了,这里就说自己对硬编码的理解:

       假设整个编码的过程就是去寄存式洗衣店的过程,规则是:

        1、收拾好你的臭袜子,写好一个清单:有几双,需要用什么方法洗,用什么样的清洗剂;

序号的卡片,并且写上你想什么时候取洗干净的袜子;

放进去;

卡片给他,他便会把你的东西放到洗衣机中;

序号;

序号把你的袜子拿走;

对应的硬编码MediaCodec需要做的事情是:

        1、对编码器进行初始化操作,告诉它你想要什么类型的数据:音频/视频、声道、采样率、码率等等;

/**
     * 初始化硬编码器
     */
    private void initMediaCode() {

        try {
            //尝试是否能够进行初始化成功
            mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        // AAC 硬编码器
        MediaFormat format = new MediaFormat();
        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");//音频编码
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); //声道数(这里是数字)
        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, Constants.AUDIO_SAMPLERATE); //采样率
        format.setInteger(MediaFormat.KEY_BIT_RATE, Constants.BIT_RATE); //码率
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

        bufferInfo = new MediaCodec.BufferInfo();//记录编码完成的buffer的信息
        mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);// MediaCodec.CONFIGURE_FLAG_ENCODE 标识为编码器
        mMediaCodec.start();//开始工作
    }

    2、获取需要存放未编码数据的盒子buffer

int index=mMediaCodec.dequeueInputBuffer(-1);//拿空盒子,index 拿到的盒子序号

    3、清空原来的数据,把数据放进去

final ByteBuffer buffer=mMediaCodec.getInputBuffer(index);
            buffer.clear();
            buffer.put(buffer1);

    4、把数据交给编码器进行编码

mMediaCodec.queueInputBuffer(index,0,length,System.nanoTime()/1000,0);//往空盒子里塞要编码的数据

    5、编码的过程不需要你关注;

    6、获取编码完成的数据

outIndex=mMediaCodec.dequeueOutputBuffer(mInfo,0);//取出已经编码好的数据,outIndex 表示盒子的位置
            ByteBuffer buffer=mMediaCodec.getOutputBuffer(outIndex);

其中2-6是一个完整的过程,完整的代码如下:

/**
     * 进行实际编码
     * @param buffer1
     * @param length
     */
    public void hardEncoder(final byte[] buffer1, final int length) {

        //这里的处理就是和之前传送带取盒子放原料的流程一样了,注意一般在子线程中循环处理
        int index=mMediaCodec.dequeueInputBuffer(-1);//拿空盒子,index 拿到的盒子序号
        if(index>=0){
            final ByteBuffer buffer=mMediaCodec.getInputBuffer(index);
            buffer.clear();
            buffer.put(buffer1);
            if(length>0){
                mMediaCodec.queueInputBuffer(index,0,length,System.nanoTime()/1000,0);//往空盒子里塞要编码的数据
            }
        }
        MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();
        int outIndex;
        //每次取出的时候,把所有加工好的都循环取出来
        do{
            outIndex=mMediaCodec.dequeueOutputBuffer(mInfo,0);//取出已经编码好的数据,outIndex 表示盒子的位置
            if(outIndex>=0){
                ByteBuffer buffer=mMediaCodec.getOutputBuffer(outIndex);
                buffer.position(mInfo.offset);
                //AAC编码,需要加数据头,AAC编码数据头固定为7个字节
                byte[] temp=new byte[mInfo.size+7];
                buffer.get(temp,7,mInfo.size);
                addADTStoPacket(temp,temp.length, Constants.AUDIO_SAMPLERATE, 1);//temp是处理后的acc数据,你可以把temp存储成一个文件就是一个acc的音频文件
                mMediaCodec.releaseOutputBuffer(outIndex,false);
            }else if(outIndex ==MediaCodec.INFO_TRY_AGAIN_LATER){
                //TODO something
            }else if(outIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
                //TODO something
            }
        }while (outIndex>=0);
    }

三、编码后的数据进行处理

上面有个方法是addADTStoPacket()这个方法就是用来给编码之后的音频数据进行头数据的添加,否则不能够进行播放;具体如下:

/**
     * 添加头部信息
     * Add ADTS header at the beginning of each and every AAC packet. This is
     * needed as MediaCodec encoder generates a packet of raw AAC data.
     * Note the packetLen must count in the ADTS header itself.
     * packet 数据
     * packetLen 数据长度
     * sampleInHz 采样率
     * chanCfgCounts 通道数
     **/
    private void addADTStoPacket(byte[] packet, int packetLen, int sampleInHz, int chanCfgCounts) {
        int profile = 2; // AAC LC
        int freqIdx = 8; // 16KHz    39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;

        switch (sampleInHz) {
            case 8000: {
                freqIdx = 11;
                break;
            }
            case 16000: {
                freqIdx = 8;
                break;
            }
            default:
                break;
        }
        int chanCfg = chanCfgCounts; // 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;

    }

对于添加之后的数据可以进行保存了!

完整的代码:

package com.gufeilong.media;

import android.annotation.TargetApi;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Environment;

import com.gufeilong.utils.Constants;
import com.gufeilong.utils.MyLog;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;

import static com.gufeilong.Activitys.TestActivity.isStop;

/**
 * Created by Gu Feilong on 18-3-20.
 * <p>
 * 音频硬编码步骤思路:
 * 1、获取到音频数据;
 * 2、使用硬编码对数据进行编码;
 * 3、保存编码后的数据为acc文件(需要对编码后的数据添加acc头文件);
 * <p>
 * 4、当需要进行与图像合成视频时则需要使用合成类?
 */

public class MyTestAudioEncorder {
    private AudioRecord audioRecord;

    private final int nInputChannels, sampleRate;
    private byte[] inBuffer;
    public static final int SAMPLES_PER_FRAME = 2048;    // AAC, bytes/frame/channel
    public static final int FRAMES_PER_BUFFER = 25;    // AAC, frame/buffer/sec
    private MediaCodec.BufferInfo bufferInfo = null;//存储的信息
    protected MediaCodec mMediaCodec;        // API >= 16(Android4.1.2)
    private String TAG = MyTestAudioEncorder.class.getSimpleName();

    /**
     * 录音设备来源
     */
    private static final int[] AUDIO_SOURCES = new int[]{
            MediaRecorder.AudioSource.MIC,//麦克风
            MediaRecorder.AudioSource.DEFAULT,//默认
            MediaRecorder.AudioSource.CAMCORDER,
            MediaRecorder.AudioSource.VOICE_COMMUNICATION,
            MediaRecorder.AudioSource.VOICE_RECOGNITION,
    };


    public MyTestAudioEncorder() {
        this.nInputChannels = Constants.AUDIO_CHANNEL;//声道
        this.sampleRate = Constants.AUDIO_SAMPLERATE;// 采样率
        initAudioRecord();
        initMediaCode();
    }

    /**
     * 第一步初始化音频采集
     */
    public void initAudioRecord() {

        //获取最小音频buffer,给音频设置缓存区不能小于这个大小
        int min_buffer_size = AudioRecord.getMinBufferSize(Constants.AUDIO_CHANNEL, Constants.AUDIO_SAMPLERATE,
                AudioFormat.ENCODING_PCM_16BIT);

        //用来获取最终的缓存区大小 SAMPLES_PER_FRAME 每帧的采样率 FRAMES_PER_BUFFER 每次缓存的帧数
        int buffer_size = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER;
        if (buffer_size < min_buffer_size)
            buffer_size = ((min_buffer_size / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2;// TODO: 18-3-29  未理解为何这里要乘2?

        //用来缓存音频数据的buffer
        inBuffer = new byte[SAMPLES_PER_FRAME];

        //根据音频的来源进行初始化:麦克风,或者其他
        for (final int source : AUDIO_SOURCES) {
            try {
                //初始化AudioRecord,音频来源,采样率,声道,返回的音频格式,缓存区的大小
                audioRecord = new AudioRecord(source, this.sampleRate, this.nInputChannels,
                        AudioFormat.ENCODING_PCM_16BIT, buffer_size);
                if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED)
                    audioRecord = null;
            } catch (final Exception e) {
                audioRecord = null;
            }
            if (audioRecord != null) break;
        }
    }

    /**
     * 初始化硬编码器
     */
    private void initMediaCode() {

        try {
            //尝试是否能够进行初始化成功
            mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        // AAC 硬编码器
        MediaFormat format = new MediaFormat();
        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");//音频编码
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); //声道数(这里是数字)
        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, Constants.AUDIO_SAMPLERATE); //采样率
        format.setInteger(MediaFormat.KEY_BIT_RATE, Constants.BIT_RATE); //码率
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

        bufferInfo = new MediaCodec.BufferInfo();//记录编码完成的buffer的信息
        mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);// MediaCodec.CONFIGURE_FLAG_ENCODE 标识为编码器
        mMediaCodec.start();//开始工作
    }

    /**
     * 开始对采集到的音频进行硬编码
     */
    public void startEncodec() {
        //开始进行
        if (audioRecord != null && audioRecord.getState() != AudioRecord.STATE_UNINITIALIZED) {
            audioRecord.startRecording();//开始进行录音

            while (true && !isStop) {
                try {
                    Arrays.fill(inBuffer, (byte) 0);//把原来的数据清空
                    int err = audioRecord.read(inBuffer, 0, inBuffer.length);//开始从audioRecord中读pcm输出出来
                    if (err <= 0) {
                        if (err == AudioRecord.ERROR_INVALID_OPERATION) {
                            MyLog.e("OF", "AudioRecord error ERROR_INVALID_OPERATION");
                            return;
                        }
                        if (err == AudioRecord.ERROR_BAD_VALUE) {
                            MyLog.e("OF", "AudioRecord error ERROR_BAD_VALUE");
                            return;
                        }
                        if (err == 0) {
                            MyLog.e("OF", "AudioRecord error 0 samples read");
                            return;
                        }
                    } else {
                        hardEncoder(inBuffer, inBuffer.length);
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private ByteBuffer[] inputBuffers;   //可用编码的bytebuffer的数组
    private ByteBuffer[] outputBuffers;  //编码完成的bytebuffer的数组
    private ByteBuffer inputBuffer;
    private ByteBuffer outputBuffer;
    private int inputBufferIndex;        //可用的编码的bytebuffer的数组的索引
    private int outputBufferIndex;       //可用的编码完成的bytebuffer的数组的索引
    protected static final int TIMEOUT_USEC = 1000;  // 10[msec]

    public static final String MIME_TYPE = "audio/mp4a-latm";

    /**
     * 进行实际编码
     * @param buffer1
     * @param length
     */
    public void hardEncoder(final byte[] buffer1, final int length) {

        //之前的音频录制是直接循环读取,然后写入文件,这里需要做编码处理再写入文件
        //这里的处理就是和之前传送带取盒子放原料的流程一样了,注意一般在子线程中循环处理
        int index=mMediaCodec.dequeueInputBuffer(-1);//拿空盒子,index 拿到的盒子序号
        if(index>=0){
            final ByteBuffer buffer=mMediaCodec.getInputBuffer(index);
            buffer.clear();
            buffer.put(buffer1);
            if(length>0){
                mMediaCodec.queueInputBuffer(index,0,length,System.nanoTime()/1000,0);//往空盒子里塞要编码的数据
            }
        }
        MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();
        int outIndex;
        //每次取出的时候,把所有加工好的都循环取出来
        do{
            outIndex=mMediaCodec.dequeueOutputBuffer(mInfo,0);//取出已经编码好的数据,outIndex 表示盒子的位置
            if(outIndex>=0){
                ByteBuffer buffer=mMediaCodec.getOutputBuffer(outIndex);
                buffer.position(mInfo.offset);
                //AAC编码,需要加数据头,AAC编码数据头固定为7个字节
                byte[] temp=new byte[mInfo.size+7];
                buffer.get(temp,7,mInfo.size);
                addADTStoPacket(temp,temp.length, Constants.AUDIO_SAMPLERATE, 1);
                saveDataWithAppend(temp, temp.length, "encorded.aac");

                /
                mMediaCodec.releaseOutputBuffer(outIndex,false);
            }else if(outIndex ==MediaCodec.INFO_TRY_AGAIN_LATER){
                //TODO something
            }else if(outIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
                //TODO something
            }
        }while (outIndex>=0);
    }

    public static void saveDataWithAppend(byte[] data, int len, String fileName) {
        String filePath = Environment.getExternalStorageDirectory() + File.separator + fileName;
        try {
            FileOutputStream outputStream = new FileOutputStream(new File(filePath), true);
            outputStream.write(data, 0, len);
        } catch (FileNotFoundException e) {

        } catch (Exception e) {
        }
    }

    /**
     * 添加头部信息
     * Add ADTS header at the beginning of each and every AAC packet. This is
     * needed as MediaCodec encoder generates a packet of raw AAC data.
     * Note the packetLen must count in the ADTS header itself.
     * packet 数据
     * packetLen 数据长度
     * sampleInHz 采样率
     * chanCfgCounts 通道数
     **/
    private void addADTStoPacket(byte[] packet, int packetLen, int sampleInHz, int chanCfgCounts) {
        int profile = 2; // AAC LC
        int freqIdx = 8; // 16KHz    39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;

        switch (sampleInHz) {
            case 8000: {
                freqIdx = 11;
                break;
            }
            case 16000: {
                freqIdx = 8;
                break;
            }
            default:
                break;
        }
        int chanCfg = chanCfgCounts; // 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;

    }

    /**
     * select the first codec that match a specific MIME type
     *
     * @param mimeType
     * @return
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private static final MediaCodecInfo selectAudioCodec(final String mimeType) {

        MediaCodecInfo result = null;
        // get the list of available codecs
        final int numCodecs = MediaCodecList.getCodecCount();
        LOOP:
        for (int i = 0; i < numCodecs; i++) {
            final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
            if (!codecInfo.isEncoder()) {    // skipp decoder
                continue;
            }
            final String[] types = codecInfo.getSupportedTypes();
            for (int j = 0; j < types.length; j++) {
                if (types[j].equalsIgnoreCase(mimeType)) {
                    if (result == null) {
                        result = codecInfo;
                        break LOOP;
                    }
                }
            }
        }
        return result;
    }
}