Android用AudioRecord進行錄音
在音視頻開發(fā)中,錄音當然是必不可少的。首先我們要學會單獨的錄音功能,當然這里說的錄音是指用AudioRecord來錄音,讀取錄音原始數(shù)據(jù),讀到的就是所謂的PCM數(shù)據(jù)。對于錄音來說,最重要的幾個參數(shù)要搞明白:
1、simpleRate采樣率,采樣率就是采樣頻率,每秒鐘記錄多少個樣本。
2、channelConfig通道配置,其實就是所謂的單通道,雙通道之類的,AudioFormat.CHANNEL_IN_MONO單通道,AudioFormat.CHANNEL_IN_STEREO雙通道,這里只列了這兩種,還有其它的,可自行查閱。
3、audioFormat音頻格式,其實就是采樣的精度,每個樣本的位數(shù),AudioFormat.ENCODING_PCM_8BIT每個樣本占8位,AudioFormat.ENCODING_PCM_16BIT每個樣本占16位,這里也只用了這兩個,別的沒研究。
在學習過程中會用到的一些參數(shù),我這里封裝了一個類,如下
public class AudioParams {
enum Format {
SINGLE_8_BIT, DOUBLE_8_BIT, SINGLE_16_BIT, DOUBLE_16_BIT
}
private Format format;
int simpleRate;
AudioParams(int simpleRate, Format f) {
this.simpleRate = simpleRate;
this.format = f;
}
AudioParams(int simpleRate, int channelCount, int bits) {
this.simpleRate = simpleRate;
set(channelCount, bits);
}
int getBits() {
return (format == Format.SINGLE_8_BIT || format == Format.DOUBLE_8_BIT) ? 8 : 16;
}
int getEncodingFormat() {
return (format == Format.SINGLE_8_BIT || format == Format.DOUBLE_8_BIT) ? AudioFormat.ENCODING_PCM_8BIT :
AudioFormat.ENCODING_PCM_16BIT;
}
int getChannelCount() {return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ? 1 : 2;}
int getChannelConfig() {
return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ? AudioFormat.CHANNEL_IN_MONO :
AudioFormat.CHANNEL_IN_STEREO;
}
int getOutChannelConfig() {
return (format == Format.SINGLE_8_BIT || format == Format.SINGLE_16_BIT) ? AudioFormat.CHANNEL_OUT_MONO :
AudioFormat.CHANNEL_OUT_STEREO;
}
void set(int channelCount, int bits) {
if ((channelCount != 1 && channelCount != 2) || (bits != 8 && bits != 16)) {
throw new IllegalArgumentException("不支持其它格式 channelCount=$channelCount bits=$bits");
}
if (channelCount == 1) {
if (bits == 8) {
format = Format.SINGLE_8_BIT;
} else {
format = Format.SINGLE_16_BIT;
}
} else {
if (bits == 8) {
format = Format.DOUBLE_8_BIT;
} else {
format = Format.DOUBLE_16_BIT;
}
}
}
}
這里固定使用了單通道8位,雙通道8位,單通道16位,雙通道16位,所以用了枚舉來限制。
為了方便把錄音數(shù)據(jù)拿出來顯示、存儲,這里寫了一個回調(diào)方法如下
public interface RecordCallback {
/**
* 數(shù)據(jù)回調(diào)
*
* @param bytes 數(shù)據(jù)
* @param len 數(shù)據(jù)有效長度,-1時表示數(shù)據(jù)結(jié)束
*/
void onRecord(byte[] bytes, int len);
}
有了這些參數(shù),現(xiàn)在就可以錄音了,先看一下樣例
public void startRecord(AudioParams params, RecordCallback callback) {
int simpleRate = params.simpleRate;
int channelConfig = params.getChannelConfig();
int audioFormat = params.getEncodingFormat();
// 根據(jù)AudioRecord提供的api拿到最小緩存大小
int bufferSize = AudioRecord.getMinBufferSize(simpleRate, channelConfig, audioFormat);
//創(chuàng)建Record對象
record = new AudioRecord(MediaRecorder.AudioSource.MIC, simpleRate, channelConfig, audioFormat, bufferSize);
recordThread = new Thread(() -> {
byte[] buffer = new byte[bufferSize];
record.startRecording();
recording = true;
while (recording) {
int read = record.read(buffer, 0, bufferSize);
// 將數(shù)據(jù)回調(diào)到外部
if (read > 0 && callback != null) {
callback.onRecord(buffer, read);
}
}
if (callback != null) {
// len 為-1時表示結(jié)束
callback.onRecord(buffer, -1);
recording = false;
}
//釋放資源
release();
});
recordThread.start();
}
這個方法就是簡單的采集音頻數(shù)據(jù),這個數(shù)據(jù)就是最原始的pcm數(shù)據(jù)。
拿到pcm數(shù)據(jù)以后,如果直接保存到文件是無法直接播放的,因為這只是一堆數(shù)據(jù),沒有任何格式說明,如果想讓普通播放器可以播放,需要在文件中加入文件頭,來告訴播放器這個數(shù)據(jù)的格式,這里是直接保存成wav格式的數(shù)據(jù)。下面就是加入wav格式文件頭的方法
private static byte[] getWaveFileHeader(int totalDataLen, int sampleRate, int channelCount, int bits) {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
int fileLength = totalDataLen + 36;
header[4] = (byte) (fileLength & 0xff);
header[5] = (byte) (fileLength >> 8 & 0xff);
header[6] = (byte) (fileLength >> 16 & 0xff);
header[7] = (byte) (fileLength >> 24 & 0xff);
//WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// pcm format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channelCount;
header[23] = 0;
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) (sampleRate >> 8 & 0xff);
header[26] = (byte) (sampleRate >> 16 & 0xff);
header[27] = (byte) (sampleRate >> 24 & 0xff);
int byteRate = sampleRate * bits * channelCount / 8;
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) (byteRate >> 8 & 0xff);
header[30] = (byte) (byteRate >> 16 & 0xff);
header[31] = (byte) (byteRate >> 24 & 0xff);
// block align
header[32] = (byte) (channelCount * bits / 8);
header[33] = 0;
// bits per sample
header[34] = (byte) bits;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalDataLen & 0xff);
header[41] = (byte) (totalDataLen >> 8 & 0xff);
header[42] = (byte) (totalDataLen >> 16 & 0xff);
header[43] = (byte) (totalDataLen >> 24 & 0xff);
return header;
}
根據(jù)幾個參數(shù)設置一下文件頭,然后直接寫入錄音采集到的pcm數(shù)據(jù),就可被正常播放了。wav文件頭格式定義,可點擊這里查看或自行百度。
如果想要通過AudioRecord錄音直接保存到文件,可參考下面方法
public void startRecord(String filePath, AudioParams params, RecordCallback callback) {
int channelCount = params.getChannelCount();
int bits = params.getBits();
final boolean storeFile = filePath != null && !filePath.isEmpty();
startRecord(params, (bytes, len) -> {
if (storeFile) {
if (file == null) {
File f = new File(filePath);
if (f.exists()) {
f.delete();
}
try {
file = new RandomAccessFile(f, "rw");
file.write(getWaveFileHeader(0, params.simpleRate, channelCount, bits));
} catch (IOException e) {
e.printStackTrace();
}
}
if (len > 0) {
try {
file.write(bytes, 0, len);
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
// 因為在前面已經(jīng)寫入頭信息,所以這里要減去頭信息才是數(shù)據(jù)的長度
int length = (int) file.length() - 44;
file.seek(0);
file.write(getWaveFileHeader(length, params.simpleRate, channelCount, bits));
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (callback != null) {
callback.onRecord(bytes, len);
}
});
}
先通過RandomAccessFile創(chuàng)建文件,先寫入文件頭,由于暫時我們不知道會錄多長,有多少pcm數(shù)據(jù),長度先用0表示,等錄音結(jié)束后,通過seek(int)方法重新寫入文件頭信息,也可以先把pcm數(shù)據(jù)保存到臨時文件,然后再寫入到一個新的文件中,這里就不舉例說明了。
最后放入完整類的代碼
package cn.sskbskdrin.record.audio;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @author sskbskdrin
* @date 2019/April/3
*/
public class AudioRecordManager {
private AudioParams DEFAULT_FORMAT = new AudioParams(8000, 1, 16);
private AudioRecord record;
private Thread recordThread;
private boolean recording = false;
private RandomAccessFile file;
public void startRecord(String filePath, RecordCallback callback) {
startRecord(filePath, DEFAULT_FORMAT, callback);
}
public void startRecord(String filePath, AudioParams params, RecordCallback callback) {
int channelCount = params.getChannelCount();
int bits = params.getBits();
final boolean storeFile = filePath != null && !filePath.isEmpty();
startRecord(params, (bytes, len) -> {
if (storeFile) {
if (file == null) {
File f = new File(filePath);
if (f.exists()) {
f.delete();
}
try {
file = new RandomAccessFile(f, "rw");
file.write(getWaveFileHeader(0, params.simpleRate, channelCount, bits));
} catch (IOException e) {
e.printStackTrace();
}
}
if (len > 0) {
try {
file.write(bytes, 0, len);
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
// 因為在前面已經(jīng)寫入頭信息,所以這里要減去頭信息才是數(shù)據(jù)的長度
int length = (int) file.length() - 44;
file.seek(0);
file.write(getWaveFileHeader(length, params.simpleRate, channelCount, bits));
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (callback != null) {
callback.onRecord(bytes, len);
}
});
}
public void startRecord(AudioParams params, RecordCallback callback) {
int simpleRate = params.simpleRate;
int channelConfig = params.getChannelConfig();
int audioFormat = params.getEncodingFormat();
// 根據(jù)AudioRecord提供的api拿到最小緩存大小
int bufferSize = AudioRecord.getMinBufferSize(simpleRate, channelConfig, audioFormat);
//創(chuàng)建Record對象
record = new AudioRecord(MediaRecorder.AudioSource.MIC, simpleRate, channelConfig, audioFormat, bufferSize);
recordThread = new Thread(() -> {
byte[] buffer = new byte[bufferSize];
record.startRecording();
recording = true;
while (recording) {
int read = record.read(buffer, 0, bufferSize);
// 將數(shù)據(jù)回調(diào)到外部
if (read > 0 && callback != null) {
callback.onRecord(buffer, read);
}
}
if (callback != null) {
// len 為-1時表示結(jié)束
callback.onRecord(buffer, -1);
recording = false;
}
//釋放資源
release();
});
recordThread.start();
}
public void stop() {
recording = false;
}
public void release() {
recording = false;
if (record != null) {
record.stop();
record.release();
}
record = null;
file = null;
recordThread = null;
}
private static byte[] getWaveFileHeader(int totalDataLen, int sampleRate, int channelCount, int bits) {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
int fileLength = totalDataLen + 36;
header[4] = (byte) (fileLength & 0xff);
header[5] = (byte) (fileLength >> 8 & 0xff);
header[6] = (byte) (fileLength >> 16 & 0xff);
header[7] = (byte) (fileLength >> 24 & 0xff);
//WAVE
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// 'fmt ' chunk
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// pcm format = 1
header[20] = 1;
header[21] = 0;
header[22] = (byte) channelCount;
header[23] = 0;
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) (sampleRate >> 8 & 0xff);
header[26] = (byte) (sampleRate >> 16 & 0xff);
header[27] = (byte) (sampleRate >> 24 & 0xff);
int byteRate = sampleRate * bits * channelCount / 8;
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) (byteRate >> 8 & 0xff);
header[30] = (byte) (byteRate >> 16 & 0xff);
header[31] = (byte) (byteRate >> 24 & 0xff);
// block align
header[32] = (byte) (channelCount * bits / 8);
header[33] = 0;
// bits per sample
header[34] = (byte) bits;
header[35] = 0;
//data
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalDataLen & 0xff);
header[41] = (byte) (totalDataLen >> 8 & 0xff);
header[42] = (byte) (totalDataLen >> 16 & 0xff);
header[43] = (byte) (totalDataLen >> 24 & 0xff);
return header;
}
public interface RecordCallback {
/**
* 數(shù)據(jù)回調(diào)
*
* @param bytes 數(shù)據(jù)
* @param len 數(shù)據(jù)有效長度,-1時表示數(shù)據(jù)結(jié)束
*/
void onRecord(byte[] bytes, int len);
}
}
如有不對之處還請評論指正
以上就是Android用AudioRecord進行錄音的詳細內(nèi)容,更多關于Android AudioRecord的資料請關注腳本之家其它相關文章!
相關文章
Android實現(xiàn)讀取掃碼槍內(nèi)容(條形碼)
這篇文章主要為大家詳細介紹了Android實現(xiàn)讀取掃碼槍內(nèi)容、條形碼,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09
解決android viewmodel 數(shù)據(jù)刷新異常的問題
這篇文章主要介紹了解決android viewmodel 數(shù)據(jù)刷新異常的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android游戲開發(fā)學習①彈跳小球?qū)崿F(xiàn)方法
這篇文章主要介紹了Android游戲開發(fā)學習①彈跳小球?qū)崿F(xiàn)方法,涉及Android通過物理引擎BallThread類模擬小球運動的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10

