Android使用AudioRecord录制PCM音频、PCM转AAC、使用MediaRecorder直接录制AAC编码音频

Android为我们提供了两个音频处理的API:AudioRecordMediaRecorder

AudioRecord:偏底层的api
MediaRecorder:对AudioRecord进行包装的api

一、使用AudioRecord录制pcm编码的音频

  • 首先需要录音权限,API>=6.0还需要动态申请 (动态申请权限代码略过,详情见文末源码)
<uses-permission android:name="android.permission.RECORD_AUDIO" />

二、录制主要分为一下几步

  • 创建AudioRecord
  • 设置参数
    • 录音来源
    • 采样率
    • 录制的声道
    • 数据格式
    • 录制的缓冲区大小
  • 开启一个线程读取音频数据
创建一个AduioRecord工具类
public class AudioRecordUtil {

    //设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
    private final int sampleRateInHz = 44100;
    //设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
    private final int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    //音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
    private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    //录制状态
    private boolean recorderState = true;
    private byte[] buffer;
    private AudioRecord audioRecord;
    private static AudioRecordUtil audioRecordUtil = new AudioRecordUtil();

    public static AudioRecordUtil getInstance() {
        return audioRecordUtil;
    }

    private AudioRecordUtil() {
        init();
    }

    private void init() {
        int recordMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        //指定 AudioRecord 缓冲区大小
        buffer = new byte[recordMinBufferSize];
        //根据录音参数构造AudioRecord实体对象
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig,
                audioFormat, recordMinBufferSize);
    }

    /**
     * 开始录制
     */
    public void start() {
        if (audioRecord.getState() == AudioRecord.RECORDSTATE_STOPPED) {
            recorderState = true;
            audioRecord.startRecording();
            new RecordThread().start();
        }
    }

    /**
     * 停止录制
     */
    public void stop() {
        recorderState = false;
        if (audioRecord.getState() == AudioRecord.RECORDSTATE_RECORDING) {
            audioRecord.stop();
        }
        audioRecord.release();
    }

    private class RecordThread extends Thread {

        @Override
        public void run() {
            while (recorderState) {
                int read = audioRecord.read(buffer, 0, buffer.length);
                if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                    //获取到的pcm数据就是buffer了
                    Log.d("TAG", String.valueOf(buffer.length));
                }
            }
        }
    }
}
启动录音
 AudioRecordUtil.getInstance().start();
停止录音
 AudioRecordUtil.getInstance().stop();
在子线程中读取到的buffer数据就是音频数据了,还是比较简单的

三、PCM的音频原数据已经获取到了,现在就对他进行AAC编码

  • 编码器的采样率必须与录制的时候保持一致
public class PCMEncoderAAC {

    //比特率
    private final static int KEY_BIT_RATE = 96000;
    //读取数据的最大字节数
    private final static int KEY_MAX_INPUT_SIZE = 1024 * 1024;
    //声道数
    private final static int CHANNEL_COUNT = 2;
    private MediaCodec mediaCodec;
    private ByteBuffer[] encodeInputBuffers;
    private ByteBuffer[] encodeOutputBuffers;
    private MediaCodec.BufferInfo encodeBufferInfo;
    private EncoderListener encoderListener;

    public PCMEncoderAAC(int sampleRate, EncoderListener encoderListener) {
        this.encoderListener = encoderListener;
        init(sampleRate);
    }

    /**
     * 初始化AAC编码器
     */
    private void init(int sampleRate) {
        try {
            //参数对应-> mime type、采样率、声道数
            MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,
                    sampleRate, CHANNEL_COUNT);
            //比特率
            encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, KEY_BIT_RATE);
            encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, KEY_MAX_INPUT_SIZE);
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
            mediaCodec.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        } catch (IOException e) {
            e.printStackTrace();
        }

        mediaCodec.start();
        encodeInputBuffers = mediaCodec.getInputBuffers();
        encodeOutputBuffers = mediaCodec.getOutputBuffers();
        encodeBufferInfo = new MediaCodec.BufferInfo();
    }

    /**
     * @param data
     */
    public void encodeData(byte[] data) {
        //dequeueInputBuffer(time)需要传入一个时间值,-1表示一直等待,0表示不等待有可能会丢帧,其他表示等待多少毫秒
        //获取输入缓存的index
        int inputIndex = mediaCodec.dequeueInputBuffer(-1);
        if (inputIndex >= 0) {
            ByteBuffer inputByteBuf = encodeInputBuffers[inputIndex];
            inputByteBuf.clear();
            //添加数据
            inputByteBuf.put(data);
            //限制ByteBuffer的访问长度
            inputByteBuf.limit(data.length);
            //把输入缓存塞回去给MediaCodec
            mediaCodec.queueInputBuffer(inputIndex, 0, data.length, 0, 0);
        }
        //获取输出缓存的index
        int outputIndex = mediaCodec.dequeueOutputBuffer(encodeBufferInfo, 0);
        while (outputIndex >= 0) {
            //获取缓存信息的长度
            int byteBufSize = encodeBufferInfo.size;
            //添加ADTS头部后的长度
            int bytePacketSize = byteBufSize + 7;
            //拿到输出Buffer
            ByteBuffer outPutBuf = encodeOutputBuffers[outputIndex];
            outPutBuf.position(encodeBufferInfo.offset);
            outPutBuf.limit(encodeBufferInfo.offset + encodeBufferInfo.size);

            byte[] aacData = new byte[bytePacketSize];
            //添加ADTS头部
            addADTStoPacket(aacData, bytePacketSize);
            /*
            get(byte[] dst,int offset,int length):ByteBuffer从position位置开始读,读取length个byte,并写入dst下
            标从offset到offset + length的区域
             */
            outPutBuf.get(aacData, 7, byteBufSize);
            outPutBuf.position(encodeBufferInfo.offset);

            //编码成功
            if (encoderListener != null) {
                encoderListener.encodeAAC(aacData);
            }
            
            //释放
            mediaCodec.releaseOutputBuffer(outputIndex, false);
            outputIndex = mediaCodec.dequeueOutputBuffer(encodeBufferInfo, 0);
        }
    }

    /**
     * 添加ADTS头
     */
    private void addADTStoPacket(byte[] packet, int packetLen) {
        // AAC LC
        int profile = 2;
        // 44.1KHz
        int freqIdx = 4;
        // CPE
        int chanCfg = 2;
        // 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 interface EncoderListener {
        void encodeAAC(byte[] data);
    }
}
使用只需要在上面拿到pcm数据的那里调用encodeData()方法即可
  • 初始化AudioRecordUtil时也同时将编码器初始化好
private PCMEncoderAAC pcmEncoderAAC;

private void init() {
	//....
    //初始化编码器
    pcmEncoderAAC = new PCMEncoderAAC(sampleRateInHz, this);
}

@Override
public void encodeAAC(byte[] data) {
    Log.d("TAG", "AAC数据长度:" + data.length);
}
  • 开始编码
private class RecordThread extends Thread {
    @Override
    public void run() {
        while (recorderState) {
            int read = audioRecord.read(buffer, 0, buffer.length);
            if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                //获取到的pcm数据就是buffer了
                Log.d("TAG", String.valueOf(buffer.length));
                pcmEncoderAAC.encodeData(buffer);
            }
        }
    }
}

四、将编码好的音频数据写入文件中

  • 创建的文件输出流

private FileOutputStream fileOutputStream;

private void init() {
    //...
    try {
        fileOutputStream = new FileOutputStream(new File(App.getAppContext().getExternalCacheDir(), "test.aac"));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

@Override
public void encodeAAC(byte[] data) {
    Log.d("TAG", "AAC数据长度:" + data.length);
    try {
        fileOutputStream.write(data);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
生成的文件

在这里插入图片描述

五、 使用MediaRecorder直接录制AAC编码的音频到文件中

  • 这种录制方式就简单许多了
public class MediaRecordUtil {
    
    private MediaRecorder mediaRecorder;
    private static MediaRecordUtil mediaRecordUtil = new MediaRecordUtil();

    public static MediaRecordUtil getInstance() {
        return mediaRecordUtil;
    }

    private MediaRecordUtil() {
        init();
    }

    private void init() {
        mediaRecorder = new MediaRecorder();
        //配置采集方式,这里用的是麦克风的采集方式
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        //配置输出方式,这里用的是AAC
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
        //配置采样频率,频率越高月接近原始声音,Android所有设备都支持的采样频率为44100
        mediaRecorder.setAudioSamplingRate(44100);
        //配置文件的编码格式,AAC是比较通用的编码格式
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        //配置码率,这里一般通用的是96000
        mediaRecorder.setAudioEncodingBitRate(96000);
        //配置录音文件的位置
        String path = App.getAppContext().getExternalCacheDir() + "/audio.aac";
        mediaRecorder.setOutputFile(path);
    }

    /**
     * 开始录制
     */
    public void start() {
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 停止录制
     */
    public void stop() {
        mediaRecorder.stop();
    }

}
启动录音
 MediaRecordUtil.getInstance().start();
停止录音
 MediaRecordUtil.getInstance().stop();
生成的文件

在这里插入图片描述

六、使用MediaRecorder可以很方便的直接将音频流写入到文件中,那么如果我们需要像AudioRecord那样获取到实时的音频流需要怎么弄呢?

  • setOutputFile()的值传递为一个FileDescriptor文件描述符
  • 然后开启子线程去读取文件描述符的数据 就是音频流数据了
public class MediaRecordUtil {

    private MediaRecorder mediaRecorder;
    private static MediaRecordUtil mediaRecordUtil = new MediaRecordUtil();
    private ParcelFileDescriptor parcelWrite;
    private DataInputStream inputStream;
    private boolean recorderState;


    public static MediaRecordUtil getInstance() {
        return mediaRecordUtil;
    }

    private MediaRecordUtil() {
        intPipLine();
        init();
    }

    private void intPipLine() {
        try {
            ParcelFileDescriptor[] parcelFileDescriptors = ParcelFileDescriptor.createPipe();
            ParcelFileDescriptor parcelRead = new ParcelFileDescriptor(parcelFileDescriptors[0]);
            parcelWrite = new ParcelFileDescriptor(parcelFileDescriptors[1]);
            inputStream = new DataInputStream(new ParcelFileDescriptor.AutoCloseInputStream(parcelRead));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void init() {
        mediaRecorder = new MediaRecorder();
        //....
        //设置获取音频流的方式
        mediaRecorder.setOutputFile(parcelWrite.getFileDescriptor());
    }

    private class RecordThread extends Thread {
        private byte[] buffer = new byte[900];

        @Override
        public void run() {
            while (recorderState) {
                try {
                    int read = inputStream.read(buffer);
                    if (read != -1) {
                        byte[] data = Arrays.copyOfRange(buffer, 0, read);
                        Log.e("TAG", "获取到的音频数据:" + data.length);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 开始录制
     */
    public void start() {
        try {
            recorderState = true;
            new RecordThread().start();
            mediaRecorder.prepare();
            mediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 停止录制
     */
    public void stop() {
        recorderState = false;
        mediaRecorder.stop();
    }

}

Demo 下载地址

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
参考链接http://www.cnblogs.com/Amandaliu/archive/2013/02/04/2891604.html 在链接内容基础上修改了amr编码格式为aac编码格式 Android提供了两个API用于实现录音功能:android.media.AudioRecordandroid.media.MediaRecorder。 网上有很多谈论这两个类的资料。现在大致总结下: 1、AudioRecord 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音) 优点:语音的实时处理,可以用代码实现各种音频的封装 缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩 示例: 使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右(已写测试代码) 2、MediaRecorder 已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp 优点:大部分以及集成,直接调用相关接口即可,代码量小 缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件 示例: 使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K(已写测试代码) 3、音频格式比较 WAV格式:录音质量高,但是压缩率小,文件大 AAC格式:相对于mp3,AAC格式的音质更佳,文件更小;有损压缩;一般苹果或者Android SDK4.1.2(API 16)及以上版本支持播放 AMR格式:压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话录音 至于常用的mp3格式,使用MediaRecorder没有该视频格式输出。一些人的做法是使用AudioRecord录音,然后编码成wav格式,再换成mp3格式 再贴上一些测试工程。 功能描述: 1、点击“录音WAV文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.wav 2、点击“录音AMR文件”,开始录音。录音完成后,生成文件/sdcard/FinalAudio.amr 3、点击“停止录音”,停止录音,并显示录音输出文件以及该文件大小。
使用AudioRecord录制音频换成wav格式,需要进行以下步骤: 1. 设置录音参数:采样率、音频通道、编码格式等 2. 创建一个AudioRecord对象 3. 开始录制音频,将音频数据写入到一个缓存区 4. 录制完成后,停止录音并释放AudioRecord对象 5. 将缓存区中的音频数据写入到一个wav文件中 下面是一个简单的示例代码,演示如何使用AudioRecord录制音频并将其换成wav格式: ``` // 设置录音参数 int sampleRateInHz = 44100; int channelConfig = AudioFormat.CHANNEL_IN_MONO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 创建AudioRecord对象 AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); // 开始录音 audioRecord.startRecording(); // 定义缓存区 byte[] buffer = new byte[bufferSizeInBytes]; // 定义输出文件 String outputFileName = "output.wav"; File outputFile = new File(Environment.getExternalStorageDirectory(), outputFileName); // 定义输出 FileOutputStream outputStream = new FileOutputStream(outputFile); // 写入wav文件头 WaveHeader waveHeader = new WaveHeader(sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); waveHeader.write(outputStream); // 写入音频数据 int readSize; while ((readSize = audioRecord.read(buffer, 0, bufferSizeInBytes)) != AudioRecord.ERROR_INVALID_OPERATION) { outputStream.write(buffer, 0, readSize); } // 停止录音并释放资源 audioRecord.stop(); audioRecord.release(); // 关闭输出 outputStream.close(); ``` 在上述代码中,我们使用了一个自定义的WaveHeader类,用于生成wav文件头信息。该类的实现可以参考下面的示例代码: ``` public class WaveHeader { private int sampleRate; private int channelCount; private int audioFormat; private int audioDataLength; public WaveHeader(int sampleRate, int channelCount, int audioFormat, int audioDataLength) { this.sampleRate = sampleRate; this.channelCount = channelCount; this.audioFormat = audioFormat; this.audioDataLength = audioDataLength; } public void write(OutputStream outputStream) throws IOException { outputStream.write("RIFF".getBytes()); outputStream.write(intToByteArray(36 + audioDataLength), 0, 4); outputStream.write("WAVE".getBytes()); outputStream.write("fmt ".getBytes()); outputStream.write(intToByteArray(16), 0, 4); outputStream.write(shortToByteArray((short) 1), 0, 2); outputStream.write(shortToByteArray((short) channelCount), 0, 2); outputStream.write(intToByteArray(sampleRate), 0, 4); outputStream.write(intToByteArray(sampleRate * channelCount * audioFormat / 8), 0, 4); outputStream.write(shortToByteArray((short) (channelCount * audioFormat / 8)), 0, 2); outputStream.write(shortToByteArray((short) audioFormat), 0, 2); outputStream.write("data".getBytes()); outputStream.write(intToByteArray(audioDataLength), 0, 4); } private byte[] intToByteArray(int value) { byte[] byteArray = new byte[4]; byteArray[0] = (byte) (value & 0xff); byteArray[1] = (byte) ((value >> 8) & 0xff); byteArray[2] = (byte) ((value >> 16) & 0xff); byteArray[3] = (byte) ((value >> 24) & 0xff); return byteArray; } private byte[] shortToByteArray(short value) { byte[] byteArray = new byte[2]; byteArray[0] = (byte) (value & 0xff); byteArray[1] = (byte) ((value >> 8) & 0xff); return byteArray; } } ``` 通过以上代码,我们可以实现将AudioRecord录制音频换成wav格式并保存到文件中。需要注意的是,由于Android 6.0及以上版本需要动态获取录音权限,因此在使用前需要先请求录音权限。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code-Porter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值