Android爲我們提供了兩個音頻處理的API:AudioRecord
和MediaRecorder
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();
}
}