Android用AudioRecord進(jìn)行錄音
在音視頻開(kāi)發(fā)中,錄音當(dāng)然是必不可少的。首先我們要學(xué)會(huì)單獨(dú)的錄音功能,當(dāng)然這里說(shuō)的錄音是指用AudioRecord來(lái)錄音,讀取錄音原始數(shù)據(jù),讀到的就是所謂的PCM數(shù)據(jù)。對(duì)于錄音來(lái)說(shuō),最重要的幾個(gè)參數(shù)要搞明白:
1、simpleRate采樣率,采樣率就是采樣頻率,每秒鐘記錄多少個(gè)樣本。
2、channelConfig通道配置,其實(shí)就是所謂的單通道,雙通道之類的,AudioFormat.CHANNEL_IN_MONO單通道,AudioFormat.CHANNEL_IN_STEREO雙通道,這里只列了這兩種,還有其它的,可自行查閱。
3、audioFormat音頻格式,其實(shí)就是采樣的精度,每個(gè)樣本的位數(shù),AudioFormat.ENCODING_PCM_8BIT每個(gè)樣本占8位,AudioFormat.ENCODING_PCM_16BIT每個(gè)樣本占16位,這里也只用了這兩個(gè),別的沒(méi)研究。
在學(xué)習(xí)過(guò)程中會(huì)用到的一些參數(shù),我這里封裝了一個(gè)類,如下
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位,所以用了枚舉來(lái)限制。
為了方便把錄音數(shù)據(jù)拿出來(lái)顯示、存儲(chǔ),這里寫了一個(gè)回調(diào)方法如下
public interface RecordCallback {
/**
* 數(shù)據(jù)回調(diào)
*
* @param bytes 數(shù)據(jù)
* @param len 數(shù)據(jù)有效長(zhǎng)度,-1時(shí)表示數(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對(duì)象
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時(shí)表示結(jié)束
callback.onRecord(buffer, -1);
recording = false;
}
//釋放資源
release();
});
recordThread.start();
}
這個(gè)方法就是簡(jiǎn)單的采集音頻數(shù)據(jù),這個(gè)數(shù)據(jù)就是最原始的pcm數(shù)據(jù)。
拿到pcm數(shù)據(jù)以后,如果直接保存到文件是無(wú)法直接播放的,因?yàn)檫@只是一堆數(shù)據(jù),沒(méi)有任何格式說(shuō)明,如果想讓普通播放器可以播放,需要在文件中加入文件頭,來(lái)告訴播放器這個(gè)數(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ù)幾個(gè)參數(shù)設(shè)置一下文件頭,然后直接寫入錄音采集到的pcm數(shù)據(jù),就可被正常播放了。wav文件頭格式定義,可點(diǎn)擊這里查看或自行百度。
如果想要通過(guò)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 {
// 因?yàn)樵谇懊嬉呀?jīng)寫入頭信息,所以這里要減去頭信息才是數(shù)據(jù)的長(zhǎng)度
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);
}
});
}
先通過(guò)RandomAccessFile創(chuàng)建文件,先寫入文件頭,由于暫時(shí)我們不知道會(huì)錄多長(zhǎng),有多少pcm數(shù)據(jù),長(zhǎng)度先用0表示,等錄音結(jié)束后,通過(guò)seek(int)方法重新寫入文件頭信息,也可以先把pcm數(shù)據(jù)保存到臨時(shí)文件,然后再寫入到一個(gè)新的文件中,這里就不舉例說(shuō)明了。
最后放入完整類的代碼
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 {
// 因?yàn)樵谇懊嬉呀?jīng)寫入頭信息,所以這里要減去頭信息才是數(shù)據(jù)的長(zhǎng)度
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對(duì)象
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時(shí)表示結(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ù)有效長(zhǎng)度,-1時(shí)表示數(shù)據(jù)結(jié)束
*/
void onRecord(byte[] bytes, int len);
}
}
如有不對(duì)之處還請(qǐng)?jiān)u論指正
以上就是Android用AudioRecord進(jìn)行錄音的詳細(xì)內(nèi)容,更多關(guān)于Android AudioRecord的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android LayerDrawable超詳細(xì)講解
一個(gè)LayerDrawable是一個(gè)可以管理一組drawable對(duì)象的drawable。在LayerDrawable的drawable資源按照列表的順序繪制,所以列表的最后一個(gè)drawable繪制在最上層2022-11-11
Flutter?DateTime日期轉(zhuǎn)換的詳細(xì)使用
本文主要介紹了Flutter?DateTime日期轉(zhuǎn)換的詳細(xì)使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
AndroidManifest.xml <uses-feature>和<uses-permisstio
這篇文章主要介紹了AndroidManifest.xml <uses-feature>和<uses-permisstion>分析及比較的相關(guān)資料,需要的朋友可以參考下2017-06-06
Android自定義View實(shí)現(xiàn)角度選擇器
前幾天在Google Photos查看照片,用了一下它的圖片剪裁功能,于是我馬上就被其界面和操作吸引。后來(lái)想模仿做一個(gè)和Google Photos裁圖頁(yè)面幾乎一模一樣的角度選擇器,本文比較基礎(chǔ),在閱讀本文前只需要掌握最基礎(chǔ)的自定義View知識(shí)和Android事件知識(shí)。下面來(lái)一起學(xué)習(xí)下吧。2016-11-11
詳解OpenGL Shader彩虹條紋效果的實(shí)現(xiàn)
這篇文章主要為大家介紹了如何通過(guò)OpenGL Shader實(shí)現(xiàn)彩虹條紋效果,最后的效果和圖片處理軟件colorow中的彩虹效果濾鏡相似,需要的可以參考一下2022-02-02
Material Design系列之Behavior實(shí)現(xiàn)支付密碼彈窗和商品屬性選擇效果
這篇文章主要為大家詳細(xì)介紹了Material Design系列之Behavior實(shí)現(xiàn)支付密碼彈窗和商品屬性選擇效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android 三種動(dòng)畫詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 三種動(dòng)畫詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04

