AudioRecord 是 Android 提供的用于实现录音功能,录制得到无损的PCM音频数据。
从AudioRecord构造函数就可以看出:
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig,
int audioFormat, int bufferSizeInBytes)
private final static int AudioSource = MediaRecorder.AudioSource.DEFAULT;
private final static int AudioRate = 44100;
private final static int AudioInChannel = AudioFormat.CHANNEL_IN_MONO;
private final static int AudioOutChannel = AudioFormat.CHANNEL_OUT_MONO;
private final static int AudioFormater = AudioFormat.ENCODING_PCM_16BIT;
private AudioRecord mAudioRecord;
private int recordBufferMinSize;
private AudioTrack mAudioTrack;
private void startRecord() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, MICROPHONE, 2);
return;
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, STORAGE, 2);
return;
}
isRecording = true;
mExecutor.execute(new RecordRunnable()); // mExecutor是一个简单的线程池
}
private class RecordRunnable implements Runnable {
private OutputStream mOutputStream;
private byte[] bufferbytes;
private File mFile;
public RecordRunnable() {
try {
mFile = getRecordFile(true);
bufferbytes = new byte[recordBufferMinSize];
mOutputStream = new FileOutputStream(mFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
mAudioRecord.startRecording();
while(isRecording) {
int readSize = mAudioRecord.read(bufferbytes, 0, recordBufferMinSize);
if (readSize > 0) {
mOutputStream.write(bufferbytes, 0, readSize);
}
}
mOutputStream.close();
View contentView = RecordingActivity.this.getWindow().getDecorView().findViewById(android.R.id.content);
contentView.post(new Runnable() {
@Override
public void run() {
Toast.makeText(RecordingActivity.this, "录制完成: " + mFile.getAbsolutePath(),Toast.LENGTH_SHORT).show();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
};
private void stopRecord() {
if (isRecording) {
isRecording = false;
mAudioRecord.stop();
}
}
Android中可以使用AudioTrack播放PCM,播放的流程跟AudioRecord很类似:
AudioTrack 有两种数据加载模式:MODE_STREAM 和 MODE_STATIC,
MODE_STREAM 数据加载模式,将音频数据不断写入AudioTrack中,缺点是会有延迟
MODE_STATIC 音频流类型,将音频数据一次性写入AudioTrack中,不会有延迟,适合小文件,缺点是对大文件可能内存不足,需要 先write写数据,最后再调用 play()
playBufferMinSize = AudioTrack.getMinBufferSize(AudioRate, AudioOutChannel, AudioFormater);
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AudioRate, AudioOutChannel, AudioFormater, playBufferMinSize, AudioTrack.MODE_STREAM);
####2 播放
private void playPcm() {
isPlaying = true;
mExecutor.execute(new PlayerRunnable(PlayerMode.PCM));
}
public enum PlayerMode { WAV, PCM};
private class PlayerRunnable implements Runnable {
private InputStream mInputStream;
private File mFile;
private byte[] bufferbytes;
private RecordingActivity.PlayerMode playerMode;
public PlayerRunnable(RecordingActivity.PlayerMode playerMode) {
this.playerMode = playerMode;
}
@Override
public void run() {
View contentView = RecordingActivity.this.getWindow().getDecorView().findViewById(android.R.id.content);
try {
mFile = playerMode == RecordingActivity.PlayerMode.PCM ? getPlayerFile() : getWAVFile();
if (mFile == null || !mFile.exists()) {
contentView.post(new Runnable() {
@Override
public void run() {
Toast.makeText(RecordingActivity.this, "音频不存在", Toast.LENGTH_SHORT).show();
}
});
return;
}
mAudioTrack.play();
bufferbytes = new byte[playBufferMinSize];
mInputStream = new FileInputStream(mFile);
if (playerMode == WAV) {
mInputStream.skip(44); // 去除WAV头部
}
while (mInputStream.available() > 0) {
int readSize = mInputStream.read(bufferbytes);
mAudioTrack.write(bufferbytes, 0, readSize);
}
mInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
mAudioTrack.stop();
isPlaying = false;
}
}
在PCM增加WAV的头部就可以了,因为pcm 是wav 文件中音频数据的一种编码方式,事实上 wav 还有很多其它的方式编码
public class Pcm2WavUtil {
private int mSampleRate;
private int minBufferSize;
public Pcm2WavUtil(int mSampleRate, int mChannels, int mFormater) {
this.mSampleRate = mSampleRate;
//this.mChannels = mChannels;
this.minBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannels, mFormater);
}
public void pcm2wav(File inFile, File outFile) {
FileInputStream pcmFileStream;
FileOutputStream wavFileStream;
byte[] buffer = new byte[minBufferSize];
try {
wavFileStream = new FileOutputStream(inFile);
pcmFileStream = new FileInputStream(outFile);
long pcmLen = pcmFileStream.getChannel().size();
byte[] head = wavHeader(pcmLen, 1, mSampleRate, 16);
wavFileStream.write(head);
while(pcmFileStream.available() > 0) {
int readSize = pcmFileStream.read(buffer);
wavFileStream.write(buffer);
}
pcmFileStream.close();
wavFileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param pcmLen pcm 数据长度
* @param numChannels 声道设置, mono = 1, stereo = 2
* @param sampleRate 采样频率
* @param bitPerSample 单次数据长度, 例如 8bits
* @return wav 头部信息
*/
public static byte[] wavHeader(long pcmLen, int numChannels, int sampleRate, int bitPerSample) {
byte[] header = new byte[44];
// ChunkID, RIFF, 占 4bytes
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
// ChunkSize, pcmLen + 36, 占 4bytes
long chunkSize = pcmLen + 36;
header[4] = (byte) (chunkSize & 0xff);
header[5] = (byte) ((chunkSize >> 8) & 0xff);
header[6] = (byte) ((chunkSize >> 16) & 0xff);
header[7] = (byte) ((chunkSize >> 24) & 0xff);
// Format, WAVE, 占 4bytes
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// Subchunk1ID, 'fmt', 占 4bytes
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// Subchunk1Size, 16, 占 4bytes
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// AudioFormat, pcm = 1, 占 2bytes
header[20] = 1;
header[21] = 0;
// NumChannels, mono = 1, stereo = 2, 占 2bytes
header[22] = (byte) numChannels;
header[23] = 0;
// SampleRate, 占 4bytes
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
// ByteRate = SampleRate * NumChannels * BitsPerSample / 8, 占 4bytes
long byteRate = sampleRate * numChannels * bitPerSample / 8;
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// BlockAlign = NumChannels * BitsPerSample / 8, 占 2bytes
header[32] = (byte) (numChannels * bitPerSample / 8);
header[33] = 0;
// BitsPerSample, 占 2bytes
header[34] = (byte) bitPerSample;
header[35] = 0;
// Subhunk2ID, data, 占 4bytes
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
// Subchunk2Size, 占 4bytes
header[40] = (byte) (pcmLen & 0xff);
header[41] = (byte) ((pcmLen >> 8) & 0xff);
header[42] = (byte) ((pcmLen >> 16) & 0xff);
header[43] = (byte) ((pcmLen >> 24) & 0xff);
return header;
}
}
与播放pcm的代码一致,只需要过滤掉wav的头部 mInputStream.skip(44)