您好,登錄后才能下訂單哦!
原文鏈接:https://www.jianshu.com/p/5966ed7c5baf
介紹音頻的采集、編碼、生成文件、轉(zhuǎn)碼等操作,通過(guò) AudioRecord 采集音頻,生成三種格式的文件格式(pcm、wav、aac),用 AudioStack 來(lái)播放這個(gè)音頻。
這里簡(jiǎn)單的介紹一下常見(jiàn)的三種音頻格式:
PCM :PCM(Pulse Code Modulation—-脈碼調(diào)制錄音)。所謂 PCM 錄音就是將聲音等模擬信號(hào)變成符號(hào)化的脈沖列,使用三個(gè)參數(shù)(聲道數(shù)、採(cǎi)樣位數(shù)和采樣頻率)來(lái)表示聲音。PCM 信號(hào)是就未經(jīng)過(guò)任何編碼和壓縮處理。與模擬信號(hào)比,它不易受傳送系統(tǒng)的雜波及失真的影響。動(dòng)態(tài)范圍寬,可得到音質(zhì)相當(dāng)好的影響效果。
WAV : WAV 是一種無(wú)損的音頻文件格式,WAV 符合 RIFF(Resource Interchange File Format) 規(guī)范。所有的 WAV 都有一個(gè)文件頭,這個(gè)文件頭音頻流的編碼參數(shù)。WAV 對(duì)音頻流的編碼沒(méi)有硬性規(guī)定,除了 PCM 之外,還有幾乎所有支持 ACM 規(guī)范的編碼都可以為 WAV 的音頻流進(jìn)行編碼。
簡(jiǎn)單來(lái)說(shuō):WAV 是一種無(wú)損的音頻文件格式,PCM是沒(méi)有壓縮的編碼方式
AAC : AAC(Advanced Audio Coding),中文稱(chēng)為“高級(jí)音頻編碼”,出現(xiàn)于 1997 年,基于 MPEG-2的音頻編碼技術(shù)。由 Fraunhofer IIS、杜比實(shí)驗(yàn)室、AT&T、Sony(索尼)等公司共同開(kāi)發(fā),目的是取代 MP3 格式。2000 年,MPEG-4 標(biāo)準(zhǔn)出現(xiàn)后,AAC 重新集成了其特性,加入了 SBR 技術(shù)和 PS 技術(shù),為了區(qū)別于傳統(tǒng)的 MPEG-2 AAC 又稱(chēng)為 MPEG-4 AAC。他是一種專(zhuān)為聲音數(shù)據(jù)設(shè)計(jì)的文件壓縮格式,與 MP3 類(lèi)似。利用 AAC 格式,可使聲音文件明顯減小,而不會(huì)讓人感覺(jué)聲音質(zhì)量有所降低 。
AudioRecord 是 Android 系統(tǒng)提供的用于實(shí)現(xiàn)錄音的功能類(lèi),要想了解這個(gè)類(lèi)的具體的說(shuō)明和用法,可以去看一下官方的文檔,如參考鏈接。
AndioRecord 類(lèi)的主要功能是讓各種 Java 應(yīng)用能夠管理音頻資源,以便它們通過(guò)此類(lèi)能夠錄制聲音相關(guān)的硬件所收集的聲音。此功能的實(shí)現(xiàn)就是通過(guò) “ pulling ”(讀?。〢udioRecord 對(duì)象的聲音數(shù)據(jù)來(lái)完成的。在錄音過(guò)程中,應(yīng)用所需要做的就是通過(guò)后面三個(gè)類(lèi)方法中的一個(gè)去及時(shí)地獲取AudioRecord對(duì)象的錄音數(shù)據(jù). AudioRecord類(lèi)提供的三個(gè)獲取聲音數(shù)據(jù)的方法分別是:
無(wú)論選擇使用那一個(gè)方法都必須事先設(shè)定方便用戶(hù)的聲音數(shù)據(jù)的存儲(chǔ)格式。
開(kāi)始錄音的時(shí)候,AudioRecord 需要初始化一個(gè)相關(guān)聯(lián)的聲音 buffer, 這個(gè) buffer 主要是用來(lái)保存新的聲音數(shù)據(jù)。這個(gè) buffer 的大小,我們可以在對(duì)象構(gòu)造期間去指定。它表明一個(gè) AudioRecord 對(duì)象還沒(méi)有被讀?。ㄍ剑┞曇魯?shù)據(jù)前能錄多長(zhǎng)的音(即一次可以錄制的聲音容量)。聲音數(shù)據(jù)從音頻硬件中被讀出,數(shù)據(jù)大小不超過(guò)整個(gè)錄音數(shù)據(jù)的大?。梢苑侄啻巫x出),即每次讀取初始化 buffer 容量的數(shù)據(jù)。
主要是聲明一些用到的參數(shù),具體解釋可以看注釋。
/指定音頻源 這個(gè)和MediaRecorder是相同的 MediaRecorder.AudioSource.MIC指的是麥克風(fēng)
private static final int mAudioSource = MediaRecorder.AudioSource.MIC;
//指定采樣率 (MediaRecoder 的采樣率通常是8000Hz,16000Hz
//AAC的通常是 44100Hz。 設(shè)置采樣率為 44100,目前為常用的采樣率,官方文檔表示這個(gè)值可以兼容所有的設(shè)置)
private static final int mSampleRateInHz = 44100;
//指定捕獲音頻的聲道數(shù)目。在 AudioFormat 類(lèi)中指定用于此的常量,單聲道
private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
//指定音頻量化位數(shù) ,在 AudioFormat 類(lèi)中指定了以下各種可能的常量。通常我們選擇 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類(lèi)的getMinBufferSize方法可以獲得。
private int mBufferSizeInBytes;
// 聲明 AudioRecord 對(duì)象
private AudioRecord mAudioRecord = null;
//初始化數(shù)據(jù),計(jì)算最小緩沖區(qū)
mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
//創(chuàng)建AudioRecorder對(duì)象
mAudioRecord = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig,
mAudioFormat, mBufferSizeInBytes);
@Override
public void run() {
//標(biāo)記為開(kāi)始采集狀態(tài)
isRecording = true;
//創(chuàng)建文件
createFile();
try {
//判斷AudioRecord未初始化,停止錄音的時(shí)候釋放了,狀態(tài)就為STATE_UNINITIALIZED
if (mAudioRecord.getState() == mAudioRecord.STATE_UNINITIALIZED) {
initData();
}
//最小緩沖區(qū)
byte[] buffer = new byte[mBufferSizeInBytes];
//獲取到文件的數(shù)據(jù)流
mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));
//開(kāi)始錄音
mAudioRecord.startRecording();
//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]);
}
}
} catch (Exception e) {
Log.e(TAG, "Recording Failed");
} finally {
// 停止錄音
stopRecord();
IOUtil.close(mDataOutputStream);
}
}
權(quán)限需求:WRITE_EXTERNAL_STORAGE、READ_EXTERNAL_STORAGE(部份手機(jī)必須要申請(qǐng)這個(gè)權(quán)限)、RECORD_AUDIO
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
到現(xiàn)在基本的錄音的流程就介紹完了,但是這時(shí)候問(wèn)題來(lái)了:
按照流程走完了,數(shù)據(jù)是進(jìn)去了,但是現(xiàn)在的文件里面的內(nèi)容僅僅是最原始的音頻數(shù)據(jù),術(shù)語(yǔ)稱(chēng)為 RAW(中文解釋是“原材料”或“未經(jīng)處理的東西”),這時(shí)候,你讓播放器去打開(kāi),它既不知道保存的格式是什么,又不知道如何進(jìn)行解碼操作。當(dāng)然播放不了。
在文件的數(shù)據(jù)開(kāi)頭加入AAC HEAD 或者 AAC 數(shù)據(jù)即可,也就是文件頭。只有加上文件頭部的數(shù)據(jù),播放器才能正確的知道里面的內(nèi)容到底是什么,進(jìn)而能夠正常的解析并播放里面的內(nèi)容。
在文件的數(shù)據(jù)開(kāi)頭加入 WAVE HEAD 或者 AAC 數(shù)據(jù)即可,也就是文件頭。只有加上文件頭部的數(shù)據(jù),播放器才能正確的知道里面的內(nèi)容到底是什么,進(jìn)而能夠正常的解析并播放里面的內(nèi)容。具體的頭文件的描述,在 Play a WAV file on an AudioTrack 里面可以進(jìn)行了解。
public class WAVUtil {
/**
* PCM文件轉(zhuǎn)WAV文件
*
* @param inPcmFilePath 輸入PCM文件路徑
* @param outWavFilePath 輸出WAV文件路徑
* @param sampleRate 采樣率,例如44100
* @param channels 聲道數(shù) 單聲道:1或雙聲道:2
* @param bitNum 采樣位數(shù),8或16
*/
public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate,int channels, int bitNum) {
FileInputStream in = null;
FileOutputStream out = null;
byte[] data = new byte[1024];
try {
//采樣字節(jié)byte率
long byteRate = sampleRate * channels * bitNum / 8;
in = new FileInputStream(inPcmFilePath);
out = new FileOutputStream(outWavFilePath);
//PCM文件大小
long totalAudioLen = in.getChannel().size();
//總大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小
long totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);
int length = 0;
while ((length = in.read(data)) > 0) {
out.write(data, 0, length);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtil.close(in,out);
}
}
/**
* 輸出WAV文件
*
* @param out WAV輸出文件流
* @param totalAudioLen 整個(gè)音頻PCM數(shù)據(jù)大小
* @param totalDataLen 整個(gè)數(shù)據(jù)大小
* @param sampleRate 采樣率
* @param channels 聲道數(shù)
* @param byteRate 采樣字節(jié)byte率
* @throws IOException
*/
private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);//數(shù)據(jù)大小
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';//WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
//FMT Chunk
header[12] = 'f'; // 'fmt '
header[13] = 'm';
header[14] = 't';
header[15] = ' ';//過(guò)渡字節(jié)
//數(shù)據(jù)大小
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
//編碼方式 10H為PCM編碼格式
header[20] = 1; // format = 1
header[21] = 0;
//通道數(shù)
header[22] = (byte) channels;
header[23] = 0;
//采樣率,每個(gè)通道的播放速度
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
//音頻數(shù)據(jù)傳送速率,采樣率*通道數(shù)*采樣深度/8
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// 確定系統(tǒng)一次要處理多少個(gè)這樣字節(jié)的數(shù)據(jù),確定緩沖區(qū),通道數(shù)*采樣位數(shù)
header[32] = (byte) (channels * 16 / 8);
header[33] = 0;
//每個(gè)樣本的數(shù)據(jù)位數(shù)
header[34] = 16;
header[35] = 0;
//Data chunk
header[36] = 'd';//data
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}
然后生成了相對(duì)的 WAV 文件,我們用用手機(jī)自帶播放器打開(kāi)此時(shí)就能正常播放,但是我們發(fā)現(xiàn)他的大小比較大,我們看到就是幾分鐘就這么大,我們平時(shí)用的是 mp3 、aac 格式的,我們?nèi)绾无k到的呢?
生成 aac 文件播放
public class AACUtil {
...
/**
* 初始化AAC編碼器
*/
private void initAACMediaEncode() {
try {
//參數(shù)對(duì)應(yīng)-> mime type、采樣率、聲道數(shù)
MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 16000, 1);
encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);//比特率
encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024);//作用于inputBuffer的大小
mediaEncode = MediaCodec.createEncoderByType(encodeType);
mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IOException e) {
e.printStackTrace();
}
if (mediaEncode == null) {
LogUtil.e("create mediaEncode failed");
return;
}
mediaEncode.start();
encodeInputBuffers = mediaEncode.getInputBuffers();
encodeOutputBuffers = mediaEncode.getOutputBuffers();
encodeBufferInfo = new MediaCodec.BufferInfo();
}
private boolean codeOver = false;
/**
* 開(kāi)始轉(zhuǎn)碼
* 音頻數(shù)據(jù){@link #srcPath}先解碼成PCM PCM數(shù)據(jù)在編碼成MediaFormat.MIMETYPE_AUDIO_AAC音頻格式
* mp3->PCM->aac
*/
public void startAsync() {
LogUtil.w("start");
new Thread(new DecodeRunnable()).start();
}
/**
* 解碼{@link #srcPath}音頻文件 得到PCM數(shù)據(jù)塊
*
* @return 是否解碼完所有數(shù)據(jù)
*/
private void srcAudioFormatToPCM() {
File file = new File(srcPath);// 指定要讀取的文件
FileInputStream fio = null;
try {
fio = new FileInputStream(file);
byte[] bb = new byte[1024];
while (!codeOver) {
if (fio.read(bb) != -1) {
LogUtil.e("============ putPCMData ============" + bb.length);
dstAudioFormatFromPCM(bb);
} else {
codeOver = true;
}
}
fio.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private byte[] chunkAudio = new byte[0];
/**
* 編碼PCM數(shù)據(jù) 得到AAC格式的音頻文件
*/
private void dstAudioFormatFromPCM(byte[] pcmData) {
int inputIndex;
ByteBuffer inputBuffer;
int outputIndex;
ByteBuffer outputBuffer;
int outBitSize;
int outPacketSize;
byte[] PCMAudio;
PCMAudio = pcmData;
encodeInputBuffers = mediaEncode.getInputBuffers();
encodeOutputBuffers = mediaEncode.getOutputBuffers();
encodeBufferInfo = new MediaCodec.BufferInfo();
inputIndex = mediaEncode.dequeueInputBuffer(0);
inputBuffer = encodeInputBuffers[inputIndex];
inputBuffer.clear();
inputBuffer.limit(PCMAudio.length);
inputBuffer.put(PCMAudio);//PCM數(shù)據(jù)填充給inputBuffer
mediaEncode.queueInputBuffer(inputIndex, 0, PCMAudio.length, 0, 0);//通知編碼器 編碼
outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);
while (outputIndex > 0) {
outBitSize = encodeBufferInfo.size;
outPacketSize = outBitSize + 7;//7為ADT頭部的大小
outputBuffer = encodeOutputBuffers[outputIndex];//拿到輸出Buffer
outputBuffer.position(encodeBufferInfo.offset);
outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
chunkAudio = new byte[outPacketSize];
addADTStoPacket(chunkAudio, outPacketSize);//添加ADTS
outputBuffer.get(chunkAudio, 7, outBitSize);//將編碼得到的AAC數(shù)據(jù) 取出到byte[]中
try {
//錄制aac音頻文件,保存在手機(jī)內(nèi)存中
bos.write(chunkAudio, 0, chunkAudio.length);
bos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
outputBuffer.position(encodeBufferInfo.offset);
mediaEncode.releaseOutputBuffer(outputIndex, false);
outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);
}
}
/**
* 添加ADTS頭
*
* @param packet
* @param packetLen
*/
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2; // AAC LC
int freqIdx = 8; // 16KHz
int chanCfg = 1; // CPE
// fill in ADTS data
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF1;
packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
packet[6] = (byte) 0xFC;
}
/**
* 釋放資源
*/
public void release() {
...
}
/**
* 解碼線(xiàn)程
*/
private class DecodeRunnable implements Runnable {
@Override
public void run() {
srcAudioFormatToPCM();
}
}
}
AudioTrack 類(lèi)可以完成 Android 平臺(tái)上音頻數(shù)據(jù)的輸出任務(wù)。AudioTrack 有兩種數(shù)據(jù)加載模式(MODE_STREAM 和 MODE_STATIC),對(duì)應(yīng)的是數(shù)據(jù)加載模式和音頻流類(lèi)型, 對(duì)應(yīng)著兩種完全不同的使用場(chǎng)景。
播放聲音可以用 MediaPlayer 和 AudioTrack,兩者都提供了 Java API 供應(yīng)用開(kāi)發(fā)者使用。雖然都可以播放聲音,但兩者還是有很大的區(qū)別的,其中最大的區(qū)別是MediaPlayer 可以播放多種格式的聲音文件,例如 MP3,AAC,WAV,OGG,MIDI 等。MediaPlayer 會(huì)在 framework 層創(chuàng)建對(duì)應(yīng)的音頻解碼器。而 AudioTrack 只能播放已經(jīng)解碼的 PCM 流,如果對(duì)比支持的文件格式的話(huà)則是 AudioTrack 只支持 wav 格式的音頻文件,因?yàn)?wav 格式的音頻文件大部分都是 PCM 流。AudioTrack 不創(chuàng)建解碼器,所以只能播放不需要解碼的 wav 文件。
在 AudioTrack 構(gòu)造函數(shù)中,會(huì)接觸到 AudioManager.STREAM_MUSIC 這個(gè)參數(shù)。它的含義與 Android 系統(tǒng)對(duì)音頻流的管理和分類(lèi)有關(guān)。
Android 將系統(tǒng)的聲音分為好幾種流類(lèi)型,下面是幾個(gè)常見(jiàn)的:
STREAM_ALARM:警告聲
STREAM_MUSIC:音樂(lè)聲,例如 music 等
STREAM_RING:鈴聲
STREAM_SYSTEM:系統(tǒng)聲音,例如低電提示音,鎖屏音等
STREAM_VOCIE_CALL:通話(huà)聲
注意:上面這些類(lèi)型的劃分和音頻數(shù)據(jù)本身并沒(méi)有關(guān)系。例如 MUSIC 和 RING 類(lèi)型都可以是某首 MP3 歌曲。另外,聲音流類(lèi)型的選擇沒(méi)有固定的標(biāo)準(zhǔn),例如,鈴聲預(yù)覽中的鈴聲可以設(shè)置為MUSIC類(lèi)型。音頻流類(lèi)型的劃分和Audio系統(tǒng)對(duì)音頻的管理策略有關(guān)。
在計(jì)算 Buffer 分配的大小的時(shí)候,我們經(jīng)常用到的一個(gè)方法就是:getMinBufferSize。這個(gè)函數(shù)決定了應(yīng)用層分配多大的數(shù)據(jù) Buffer。
AudioTrack.getMinBufferSize(8000,//每秒8K個(gè)采樣點(diǎn)
AudioFormat.CHANNEL_CONFIGURATION_STEREO,//雙聲道
AudioFormat.ENCODING_PCM_16BIT);
從 AudioTrack.getMinBufferSize 開(kāi)始追溯代碼,可以發(fā)現(xiàn)在底層的代碼中有一個(gè)很重要的概念:Frame(幀)。Frame 是一個(gè)單位,用來(lái)描述數(shù)據(jù)量的多少。1 單位的 Frame 等于 1 個(gè)采樣點(diǎn)的字節(jié)數(shù) × 聲道數(shù)(比如 PCM16,雙聲道的 1 個(gè) Frame 等于 2×2=4 字節(jié))。1 個(gè)采樣點(diǎn)只針對(duì)一個(gè)聲道,而實(shí)際上可能會(huì)有一或多個(gè)聲道。由于不能用一個(gè)獨(dú)立的單位來(lái)表示全部聲道一次采樣的數(shù)據(jù)量,也就引出了 Frame 的概念。Frame 的大小,就是一個(gè)采樣點(diǎn)的字節(jié)數(shù) × 聲道數(shù)。另外,在目前的聲卡驅(qū)動(dòng)程序中,其內(nèi)部緩沖區(qū)也是采用 Frame 作為單位來(lái)分配和管理的。
getMinBufSize 會(huì)綜合考慮硬件的情況(諸如是否支持采樣率,硬件本身的延遲情況等)后,得出一個(gè)最小緩沖區(qū)的大小。一般我們分配的緩沖大小會(huì)是它的整數(shù)倍。
每一個(gè)音頻流對(duì)應(yīng)著一個(gè) AudioTrack 類(lèi)的一個(gè)實(shí)例,每個(gè) AudioTrack 會(huì)在創(chuàng)建時(shí)注冊(cè)到 AudioFlinger 中,由 AudioFlinger 把所有的 AudioTrack 進(jìn)行混合(Mixer),然后輸送到 AudioHardware 中進(jìn)行播放,目前 Android 同時(shí)最多可以創(chuàng)建 32 個(gè)音頻流,也就是說(shuō),Mixer 最多會(huì)同時(shí)處理 32 個(gè) AudioTrack 的數(shù)據(jù)流。
public class AudioTrackManager {
...
//音頻流類(lèi)型
private static final int mStreamType = AudioManager.STREAM_MUSIC;
//指定采樣率 (MediaRecoder 的采樣率通常是8000Hz AAC的通常是44100Hz。 設(shè)置采樣率為44100,目前為常用的采樣率,官方文檔表示這個(gè)值可以兼容所有的設(shè)置)
private static final int mSampleRateInHz = 44100;
//指定捕獲音頻的聲道數(shù)目。在AudioFormat類(lèi)中指定用于此的常量
private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //單聲道
//指定音頻量化位數(shù) ,在AudioFormaat類(lèi)中指定了以下各種可能的常量。通常我們選擇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類(lèi)的getMinBufferSize方法可以獲得。
private int mMinBufferSize;
//STREAM的意思是由用戶(hù)在應(yīng)用程序通過(guò)write方式把數(shù)據(jù)一次一次得寫(xiě)到audiotrack中。這個(gè)和我們?cè)趕ocket中發(fā)送數(shù)據(jù)一樣,
// 應(yīng)用層從某個(gè)地方獲取數(shù)據(jù),例如通過(guò)編解碼得到PCM數(shù)據(jù),然后write到audiotrack。
private static int mMode = AudioTrack.MODE_STREAM;
private void initData() {
//根據(jù)采樣率,采樣精度,單雙聲道來(lái)得到frame的大小。
mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);//計(jì)算最小緩沖區(qū)
//注意,按照數(shù)字音頻的知識(shí),這個(gè)算出來(lái)的是一秒鐘buffer的大小。
//創(chuàng)建AudioTrack
mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig,
mAudioFormat, mMinBufferSize, mMode);
}
/**
* 啟動(dòng)播放線(xiàn)程
*/
private void startThread() {
destroyThread();
isStart = true;
if (mRecordThread == null) {
mRecordThread = new Thread(recordRunnable);
mRecordThread.start();
}
}
/**
* 播放線(xiàn)程
*/
private Runnable recordRunnable = new Runnable() {
@Override
public void run() {
try {
//設(shè)置線(xiàn)程的優(yōu)先級(jí)
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
byte[] tempBuffer = new byte[mMinBufferSize];
int readCount = 0;
while (mDis.available() > 0) {
readCount = mDis.read(tempBuffer);
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
continue;
}
//一邊播放一邊寫(xiě)入語(yǔ)音數(shù)據(jù)
if (readCount != 0 && readCount != -1) {
//判斷AudioTrack未初始化,停止播放的時(shí)候釋放了,狀態(tài)就為STATE_UNINITIALIZED
if (mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED) {
initData();
}
mAudioTrack.play();
mAudioTrack.write(tempBuffer, 0, readCount);
}
}
//播放完就停止播放
stopPlay();
} catch (Exception e) {
e.printStackTrace();
}
}
};
/**
* 啟動(dòng)播放
*
* @param path
*/
public void startPlay(String path) {
try {
setPath(path);
startThread();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 停止播放
*/
public void stopPlay() {
try {
destroyThread();//銷(xiāo)毀線(xiàn)程
if (mAudioTrack != null) {
if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功
mAudioTrack.stop();//停止播放
}
if (mAudioTrack != null) {
mAudioTrack.release();//釋放audioTrack資源
}
}
if (mDis != null) {
mDis.close();//關(guān)閉數(shù)據(jù)輸入流
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。