音頻采集

想更好地了解音頻采集,首先要去了解一些音頻入門(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();

}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容