想更好地了解音頻采集,首先要去了解一些音頻入門(mén)基礎(chǔ)知識(shí)。關(guān)于一些音頻開(kāi)發(fā)的一些基礎(chǔ)知識(shí),這里就不一一講解了,可以去了解Android音頻技術(shù)開(kāi)發(fā)的一些基礎(chǔ)知識(shí)
? Android音頻采集(捕獲)
android平臺(tái)上的音頻采集一般就兩種方式:
使用MediaRecorder進(jìn)行音頻采集。
MediaRecorder 是基于 AudioRecorder 的 API(最終還是會(huì)創(chuàng)建AudioRecord用來(lái)與AudioFlinger進(jìn)行交互) ,它可以直接將采集到的音頻數(shù)據(jù)轉(zhuǎn)化為執(zhí)行的編碼格式,并保存。這種方案相較于調(diào)用系統(tǒng)內(nèi)置的用用程序,便于開(kāi)發(fā)者在UI界面上布局,而且系統(tǒng)封裝的很好,便于使用,唯一的缺點(diǎn)是使用它錄下來(lái)的音頻是經(jīng)過(guò)編碼的,沒(méi)有辦法的得到原始的音頻。同時(shí)MediaRecorder即可用于音頻的捕獲也可以用于視頻的捕獲相當(dāng)?shù)膹?qiáng)大。實(shí)際開(kāi)發(fā)中沒(méi)有特殊需求的話,用的是比較多的!
使用AudioRecord進(jìn)行音頻采集。
AudioRecord 是一個(gè)比較偏底層的API,它可以獲取到一幀幀PCM數(shù)據(jù),之后可以對(duì)這些數(shù)據(jù)進(jìn)行處理。AudioRecord這種方式采集最為靈活,使開(kāi)發(fā)者最大限度的處理采集的音頻,同時(shí)它捕獲到的音頻是原始音頻PCM格式的!像做變聲處理的需要就必須要用它收集音頻。
在實(shí)際開(kāi)發(fā)中,它也是最常用來(lái)采集音頻的手段。如直播技術(shù)采用的就是AudioRecorder采集音頻數(shù)據(jù)。AudioRecorder采集也是本文介紹的重點(diǎn)。
? AudioRecord的基本參數(shù)
audioResource:音頻采集的來(lái)源
audioSampleRate:音頻采樣率
channelConfig:聲道
audioFormat:音頻采樣精度,指定采樣的數(shù)據(jù)的格式和每次采樣的大小。
bufferSizeInBytes:AudioRecord 采集到的音頻數(shù)據(jù)所存放的緩沖區(qū)大小。
獲取最小的緩沖區(qū)大小,用于存放AudioRecord采集到的音頻數(shù)據(jù)。
//指定音頻源 這個(gè)和MediaRecorder是相同的 MediaRecorder.AudioSource.MIC指的是麥克風(fēng)
private static final int mAudioSource = MediaRecorder.AudioSource.MIC;
//指定采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz。 設(shè)置采樣率為44100,目前為常用的采樣率,官方文檔表示這個(gè)值可以兼容所有的設(shè)置)
private static final int mSampleRateInHz=44100 ;
//指定捕獲音頻的聲道數(shù)目。在AudioFormat類中指定用于此的常量
private static final int mChannelConfig= AudioFormat.CHANNEL_CONFIGURATION_MONO; //單聲道
//指定音頻量化位數(shù) ,在AudioFormaat類中指定了以下各種可能的常量。通常我們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脈沖編碼調(diào)制,它實(shí)際上是原始音頻樣本。
//因此可以設(shè)置每個(gè)樣本的分辨率為16位或者8位,16位將占用更多的空間和處理能力,表示的音頻也更加接近真實(shí)。
private static final int mAudioFormat=AudioFormat.ENCODING_PCM_16BIT;
//指定緩沖區(qū)大小。調(diào)用AudioRecord類的getMinBufferSize方法可以獲得。
private int mBufferSizeInBytes= AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig, mAudioFormat);//計(jì)算最小緩沖區(qū)
//創(chuàng)建AudioRecord。AudioRecord類實(shí)際上不會(huì)保存捕獲的音頻,因此需要手動(dòng)創(chuàng)建文件并保存下載。
private AudioRecord mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,mSampleRateInHz,mChannelConfig,
mAudioFormat, mBufferSizeInBytes);//創(chuàng)建AudioRecorder對(duì)象
以上的基本參數(shù)對(duì)于基本的音頻采集已經(jīng)夠用了,但參數(shù)還有很多就不一一說(shuō)明了,對(duì)于參數(shù)值的設(shè)置也要看項(xiàng)目設(shè)計(jì)而設(shè)置。我們了解了基本參數(shù),那就來(lái)看看AudioRecord的一些狀態(tài)。
? AudioRecord的狀態(tài)用意
AudioRecord的狀態(tài)對(duì)于我們做業(yè)務(wù)邏輯時(shí)會(huì)起到很好的作用。下面說(shuō)來(lái)說(shuō)說(shuō)一些常用的狀態(tài)。
獲取AudioRecord的狀態(tài)
用于檢測(cè)AudioRecord是否確保了獲得適當(dāng)?shù)挠布Y源。在AudioRecord對(duì)象實(shí)例化之后調(diào)用getState()。
STATE_INITIALIZED 初始完畢
STATE_UNINITIALIZED 未初始化
開(kāi)始采集時(shí)調(diào)用mAudioRecord.startRecording()開(kāi)始錄音。
開(kāi)始采集之后,狀態(tài)自動(dòng)變?yōu)镽ECORDSTATE_RECORDING。
停止采集時(shí)調(diào)用mAudioRecord.stop()停止錄音。
停止采集之后,狀態(tài)自動(dòng)變?yōu)?RECORDSTATE_STOPPED 。
讀取錄制內(nèi)容,將采集到的數(shù)據(jù)讀取到緩沖區(qū),方法調(diào)用的返回值的狀態(tài)碼。
情況異常:
ERROR_INVALID_OPERATION if the object wasn't properly initialized。
ERROR_BAD_VALUE if the parameters don't resolve to valid data and indexes。
情況正常:the number of bytes that were read。
? AudioRecord音頻采集的基本流程
構(gòu)造一個(gè) AudioRecord 對(duì)象。
開(kāi)始采集。
讀取采集的數(shù)據(jù)。
停止采集。
構(gòu)造一個(gè) AudioRecord 對(duì)象
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,mSampleRateInHz,mChannelConfig,
mAudioFormat, mBufferSizeInBytes);//創(chuàng)建AudioRecorder對(duì)象
獲取 getMinBufferSize 值
mBufferSizeInBytes 是 AudioRecord 采集到的音頻數(shù)據(jù)所存放的緩沖區(qū)大小。
mBufferSizeInBytes = AudioRecord.getMinBufferSize(audioSampleRate, channelConfig, audioFormat);
特別注意:這個(gè)大小不能隨便設(shè)置,AudioRecord 提供對(duì)應(yīng)的 API 來(lái)獲取這個(gè)值。
通過(guò) mBufferSizeInBytes 返回就可以知道傳入給AudioRecord.getMinBufferSize的參數(shù)是否支持當(dāng)前的硬件設(shè)備。
if (AudioRecord.ERROR_BAD_VALUE == mBufferSizeInBytes || AudioRecord.ERROR == mBufferSizeInBytes) {
throw new RuntimeException("Unable to getMinBufferSize");
}
//bufferSizeInBytes is available...
//或者檢測(cè)AudioRecord是否確保了獲得適當(dāng)?shù)挠布Y源。
if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
throw new RuntimeException("The AudioRecord is not uninitialized");
}
開(kāi)始采集
我們?cè)陂_(kāi)始錄音之前,首先要判斷一下AudioRecord的狀態(tài)是否已經(jīng)初始化完畢了,然后再去調(diào)用audioRecord.startRecording()。
//判斷AudioRecord的狀態(tài)是否初始化完畢
//在AudioRecord對(duì)象構(gòu)造完畢之后,就處于AudioRecord.STATE_INITIALIZED狀態(tài)了。
if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
throw new RuntimeException("The AudioRecord is not uninitialized");
}
讀取采集的數(shù)據(jù)
由于AudioRecord 在采集數(shù)據(jù)時(shí)會(huì)將數(shù)據(jù)存放到緩沖區(qū)中,因此我們只需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)流去從緩沖區(qū)中將采集的數(shù)據(jù)讀取出來(lái)即可。
創(chuàng)建一個(gè)數(shù)據(jù)流,一邊從 AudioRecord 中讀取音頻數(shù)據(jù)到緩沖區(qū),一邊將緩沖區(qū) 中數(shù)據(jù)寫(xiě)入到數(shù)據(jù)流。
因?yàn)樾枰褂肐O操作,因此讀取數(shù)據(jù)的過(guò)程應(yīng)該在子線程中執(zhí)行。
@Override
public void run() {
//標(biāo)記為開(kāi)始采集狀態(tài)
isRecording = true;
//創(chuàng)建一個(gè)流,存放從AudioRecord讀取的數(shù)據(jù)
mRecordingFile = new File(mFileRoot,mFileName);
if(mRecordingFile.exists()){//音頻文件保存過(guò)了刪除
mRecordingFile.delete();
}
try {
mRecordingFile.createNewFile();//創(chuàng)建新文件
} catch (IOException e) {
e.printStackTrace();
Log.e("lu","創(chuàng)建儲(chǔ)存音頻文件出錯(cuò)");
}
try {
//獲取到文件的數(shù)據(jù)流
mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));
byte[] buffer = new byte[mBufferSizeInBytes];
mAudioRecord.startRecording();//開(kāi)始錄音
//getRecordingState獲取當(dāng)前AudioReroding是否正在采集數(shù)據(jù)的狀態(tài)
while (isRecording && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
int bufferReadResult = mAudioRecord.read(buffer,0,mBufferSizeInBytes);
for (int i = 0; i < bufferReadResult; i++)
{
mDataOutputStream.write(buffer[i]);
}
}
mDataOutputStream.close();
} catch (Throwable t) {
Log.e("lu", "Recording Failed");
stopRecord();
}
}
停止采集
//停止錄音
public void stopRecord() {
isRecording = false;
//停止錄音,回收AudioRecord對(duì)象,釋放內(nèi)存
if (mAudioRecord != null) {
if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
mAudioRecord.stop();
}
if (mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
mAudioRecord.release();
}
}
}
注意添加權(quán)限
有時(shí)候代碼寫(xiě)完了就行動(dòng)了,很容易忽略在AndroidManifest.xml中添加權(quán)限。
? 總結(jié)
直播技術(shù)采用的就是AudioRecorder采集音頻數(shù)據(jù)。
mBufferSizeInBytes這個(gè)大小不能隨便設(shè)置,AudioRecord 提供對(duì)應(yīng)的 API 來(lái)獲取這個(gè)值。
一些AudioRecord狀態(tài)判斷是否支持當(dāng)前的硬件設(shè)備。
注意AndroidManifest.xml中添加權(quán)限。
AudioRecord保存的音頻文件為.pcm格式,不能直接播放。它是一個(gè)原始的文件,沒(méi)有任何播放格式,因此就無(wú)法被播放器識(shí)別并播放。
播放PCM文件的方案
使用 AudioTrack 播放PCM格式的音頻數(shù)據(jù)。
將PCM數(shù)據(jù)轉(zhuǎn)化為wav格式的數(shù)據(jù),這樣就可以被播放器識(shí)別。
貼出AudioRecord源代碼,寫(xiě)得不好的,請(qǐng)大家多多指正。
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
importjava.io.BufferedOutputStream;
importjava.io.DataOutputStream;
importjava.io.File;
importjava.io.FileOutputStream;
importjava.io.IOException;
public class AudioRecordActivity extends AppCompatActivity implements Runnable {
private Button mBtnStartRecord,mBtnStopRecord;
//指定音頻源 這個(gè)和MediaRecorder是相同的 MediaRecorder.AudioSource.MIC指的是麥克風(fēng)
private static final int mAudioSource = MediaRecorder.AudioSource.MIC;
//指定采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz。 設(shè)置采樣率為44100,目前為常用的采樣率,官方文檔表示這個(gè)值可以兼容所有的設(shè)置)
private static final int mSampleRateInHz=44100 ;
//指定捕獲音頻的聲道數(shù)目。在AudioFormat類中指定用于此的常量
private static final int mChannelConfig= AudioFormat.CHANNEL_CONFIGURATION_MONO; //單聲道
//指定音頻量化位數(shù) ,在AudioFormaat類中指定了以下各種可能的常量。通常我們選擇ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脈沖編碼調(diào)制,它實(shí)際上是原始音頻樣本。
//因此可以設(shè)置每個(gè)樣本的分辨率為16位或者8位,16位將占用更多的空間和處理能力,表示的音頻也更加接近真實(shí)。
private static final int mAudioFormat=AudioFormat.ENCODING_PCM_16BIT;
//指定緩沖區(qū)大小。調(diào)用AudioRecord類的getMinBufferSize方法可以獲得。
private int mBufferSizeInBytes;
private File mRecordingFile;//儲(chǔ)存AudioRecord錄下來(lái)的文件
private boolean isRecording = false; //true表示正在錄音
private AudioRecord mAudioRecord=null;
private File mFileRoot=null;//文件目錄
//存放的目錄路徑名稱
private static final String mPathName = Environment.getExternalStorageDirectory().getAbsolutePath()+ "/AudiioRecordFile";
//保存的音頻文件名
private static final String mFileName = "audiorecordtest.pcm";
//緩沖區(qū)中數(shù)據(jù)寫(xiě)入到數(shù)據(jù),因?yàn)樾枰褂肐O操作,因此讀取數(shù)據(jù)的過(guò)程應(yīng)該在子線程中執(zhí)行。
private Thread mThread;
private DataOutputStream mDataOutputStream;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_record);
initDatas();
initUI();
}
//初始化數(shù)據(jù)
private void initDatas() {
mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig, mAudioFormat);//計(jì)算最小緩沖區(qū)
mAudioRecord = new AudioRecord(mAudioSource,mSampleRateInHz,mChannelConfig,
mAudioFormat, mBufferSizeInBytes);//創(chuàng)建AudioRecorder對(duì)象
mFileRoot = new File(mPathName);
if(!mFileRoot.exists())
mFileRoot.mkdirs();//創(chuàng)建文件夾
}
//初始化UI
private void initUI() {
mBtnStartRecord = findViewById(R.id.btn_start_record);
mBtnStopRecord = findViewById(R.id.btn_stop_record);
mBtnStartRecord.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startRecord();
}
});
mBtnStopRecord.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopRecord();
}
});
}
//開(kāi)始錄音
public void startRecord() {
//AudioRecord.getMinBufferSize的參數(shù)是否支持當(dāng)前的硬件設(shè)備
if (AudioRecord.ERROR_BAD_VALUE == mBufferSizeInBytes || AudioRecord.ERROR == mBufferSizeInBytes) {
throw new RuntimeException("Unable to getMinBufferSize");
}else{
destroyThread();
isRecording = true;
if(mThread == null){
mThread = new Thread(this);
mThread.start();//開(kāi)啟線程
}
}
}
/**
* 銷毀線程方法
*/
private void destroyThread() {
try {
isRecording = false;
if (null != mThread && Thread.State.RUNNABLE == mThread.getState()) {
try {
Thread.sleep(500);
mThread.interrupt();
} catch (Exception e) {
mThread = null;
}
}
mThread = null;
} catch (Exception e) {
e.printStackTrace();
} finally {
mThread = null;
}
}
//停止錄音
public void stopRecord() {
isRecording = false;
//停止錄音,回收AudioRecord對(duì)象,釋放內(nèi)存
if (mAudioRecord != null) {
if (mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功
mAudioRecord.stop();
}
if (mAudioRecord !=null ) {
mAudioRecord.release();
}
}
}
@Override
public void run() {
//標(biāo)記為開(kāi)始采集狀態(tài)
isRecording = true;
//創(chuàng)建一個(gè)流,存放從AudioRecord讀取的數(shù)據(jù)
mRecordingFile = new File(mFileRoot,mFileName);
if(mRecordingFile.exists()){//音頻文件保存過(guò)了刪除
mRecordingFile.delete();
}
try {
mRecordingFile.createNewFile();//創(chuàng)建新文件
} catch (IOException e) {
e.printStackTrace();
Log.e("lu","創(chuàng)建儲(chǔ)存音頻文件出錯(cuò)");
}
try {
//獲取到文件的數(shù)據(jù)流
mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));
byte[] buffer = new byte[mBufferSizeInBytes];
//判斷AudioRecord未初始化,停止錄音的時(shí)候釋放了,狀態(tài)就為STATE_UNINITIALIZED
if(mAudioRecord.getState() == mAudioRecord.STATE_UNINITIALIZED){
initDatas();
}
mAudioRecord.startRecording();//開(kāi)始錄音
//getRecordingState獲取當(dāng)前AudioReroding是否正在采集數(shù)據(jù)的狀態(tài)
while (isRecording && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
int bufferReadResult = mAudioRecord.read(buffer,0,mBufferSizeInBytes);
for (int i = 0; i < bufferReadResult; i++)
{
mDataOutputStream.write(buffer[i]);
}
}
mDataOutputStream.close();
} catch (Throwable t) {
Log.e("lu", "Recording Failed");
stopRecord();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
destroyThread();
stopRecord();
}
}