Android實(shí)現(xiàn)音頻錄音與播放
本文介紹音頻的采集、編碼、生成文件、轉(zhuǎn)碼等操作,通過 AudioRecord 采集音頻,生成三種格式的文件格式(pcm、wav、aac),用 AudioStack 來播放這個(gè)音頻。
一、PCM 、WAV、AAC 的文件頭介紹
這里簡(jiǎn)單的介紹一下常見的三種音頻格式:
PCM :PCM(Pulse Code Modulation—-脈碼調(diào)制錄音)。所謂 PCM 錄音就是將聲音等模擬信號(hào)變成符號(hào)化的脈沖列,使用三個(gè)參數(shù)(聲道數(shù)、採(cǎi)樣位數(shù)和采樣頻率)來表示聲音。PCM 信號(hào)是就未經(jīng)過任何編碼和壓縮處理。與模擬信號(hào)比,它不易受傳送系統(tǒng)的雜波及失真的影響。動(dòng)態(tài)范圍寬,可得到音質(zhì)相當(dāng)好的影響效果。
WAV : WAV 是一種無損的音頻文件格式,WAV 符合 RIFF(Resource Interchange File Format) 規(guī)范。所有的 WAV 都有一個(gè)文件頭,這個(gè)文件頭音頻流的編碼參數(shù)。WAV 對(duì)音頻流的編碼沒有硬性規(guī)定,除了 PCM 之外,還有幾乎所有支持 ACM 規(guī)范的編碼都可以為 WAV 的音頻流進(jìn)行編碼。
簡(jiǎn)單來說:WAV 是一種無損的音頻文件格式,PCM是沒有壓縮的編碼方式
AAC : AAC(Advanced Audio Coding),中文稱為“高級(jí)音頻編碼”,出現(xiàn)于 1997 年,基于 MPEG-2的音頻編碼技術(shù)。由 Fraunhofer IIS、杜比實(shí)驗(yàn)室、AT&T、Sony(索尼)等公司共同開發(fā),目的是取代 MP3 格式。2000 年,MPEG-4 標(biāo)準(zhǔn)出現(xiàn)后,AAC 重新集成了其特性,加入了 SBR 技術(shù)和 PS 技術(shù),為了區(qū)別于傳統(tǒng)的 MPEG-2 AAC 又稱為 MPEG-4 AAC。他是一種專為聲音數(shù)據(jù)設(shè)計(jì)的文件壓縮格式,與 MP3 類似。利用 AAC 格式,可使聲音文件明顯減小,而不會(huì)讓人感覺聲音質(zhì)量有所降低 。
二、使用 AudioRecord 實(shí)現(xiàn)錄音生成 PCM 文件
AudioRecord 是 Android 系統(tǒng)提供的用于實(shí)現(xiàn)錄音的功能類,要想了解這個(gè)類的具體的說明和用法,可以去看一下官方的文檔,如參考鏈接。
AndioRecord 類的主要功能是讓各種 Java 應(yīng)用能夠管理音頻資源,以便它們通過此類能夠錄制聲音相關(guān)的硬件所收集的聲音。此功能的實(shí)現(xiàn)就是通過 “ pulling ”(讀?。〢udioRecord 對(duì)象的聲音數(shù)據(jù)來完成的。在錄音過程中,應(yīng)用所需要做的就是通過后面三個(gè)類方法中的一個(gè)去及時(shí)地獲取AudioRecord對(duì)象的錄音數(shù)據(jù). AudioRecord類提供的三個(gè)獲取聲音數(shù)據(jù)的方法分別是:
- read(byte[], int, int)
- read(short[], int, int)
- read(ByteBuffer, int)
無論選擇使用那一個(gè)方法都必須事先設(shè)定方便用戶的聲音數(shù)據(jù)的存儲(chǔ)格式。
開始錄音的時(shí)候,AudioRecord 需要初始化一個(gè)相關(guān)聯(lián)的聲音 buffer, 這個(gè) buffer 主要是用來保存新的聲音數(shù)據(jù)。這個(gè) buffer 的大小,我們可以在對(duì)象構(gòu)造期間去指定。它表明一個(gè) AudioRecord 對(duì)象還沒有被讀取(同步)聲音數(shù)據(jù)前能錄多長(zhǎng)的音(即一次可以錄制的聲音容量)。聲音數(shù)據(jù)從音頻硬件中被讀出,數(shù)據(jù)大小不超過整個(gè)錄音數(shù)據(jù)的大?。梢苑侄啻巫x出),即每次讀取初始化 buffer 容量的數(shù)據(jù)。
2.1 首先要聲明一些全局的變量和常量參數(shù)
主要是聲明一些用到的參數(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 類中指定用于此的常量,單聲道 private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //指定音頻量化位數(shù) ,在 AudioFormat 類中指定了以下各種可能的常量。通常我們選擇 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 對(duì)象 private AudioRecord mAudioRecord = null;
2.2 獲取 buffer 的大小并創(chuàng)建 AudioRecord
//初始化數(shù)據(jù),計(jì)算最小緩沖區(qū) mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat); //創(chuàng)建AudioRecorder對(duì)象 mAudioRecord = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?mAudioFormat, mBufferSizeInBytes);
2.3 寫文件
@Override ? ? public void run() { ? ? ? ? //標(biāo)記為開始采集狀態(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))); ? ? ? ? ? ? //開始錄音 ? ? ? ? ? ? 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); ? ? ? ? } ? ? }
2.4 權(quán)限申請(qǐng)
權(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"/>
2.5 采集小結(jié)
到現(xiàn)在基本的錄音的流程就介紹完了,但是這時(shí)候問題來了:
- 我按照流程,把音頻數(shù)據(jù)都輸出到文件里面了,停止錄音后,打開此文件,發(fā)現(xiàn)不能播放,到底是為什么呢?
按照流程走完了,數(shù)據(jù)是進(jìn)去了,但是現(xiàn)在的文件里面的內(nèi)容僅僅是最原始的音頻數(shù)據(jù),術(shù)語稱為 RAW(中文解釋是“原材料”或“未經(jīng)處理的東西”),這時(shí)候,你讓播放器去打開,它既不知道保存的格式是什么,又不知道如何進(jìn)行解碼操作。當(dāng)然播放不了。
- 那如何才能在播放器中播放我錄制的內(nèi)容呢?
在文件的數(shù)據(jù)開頭加入AAC HEAD 或者 AAC 數(shù)據(jù)即可,也就是文件頭。只有加上文件頭部的數(shù)據(jù),播放器才能正確的知道里面的內(nèi)容到底是什么,進(jìn)而能夠正常的解析并播放里面的內(nèi)容。
三、PCM 轉(zhuǎn)化為 WAV
在文件的數(shù)據(jù)開頭加入 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] = ' ';//過渡字節(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ī)自帶播放器打開此時(shí)就能正常播放,但是我們發(fā)現(xiàn)他的大小比較大,我們看到就是幾分鐘就這么大,我們平時(shí)用的是 mp3 、aac 格式的,我們?nèi)绾无k到的呢?
四、PCM 轉(zhuǎn)化為 AAC 文件格式
生成 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; ? ? /** ? ? ?* 開始轉(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() { ? ? ? ... ? ? } ? ? /** ? ? ?* 解碼線程 ? ? ?*/ ? ? private class DecodeRunnable implements Runnable { ? ? ? ? @Override ? ? ? ? public void run() { ? ? ? ? ? ? srcAudioFormatToPCM(); ? ? ? ? } ? ? } }
五、AudioStack 播放
AudioTrack 類可以完成 Android 平臺(tái)上音頻數(shù)據(jù)的輸出任務(wù)。AudioTrack 有兩種數(shù)據(jù)加載模式(MODE_STREAM 和 MODE_STATIC),對(duì)應(yīng)的是數(shù)據(jù)加載模式和音頻流類型, 對(duì)應(yīng)著兩種完全不同的使用場(chǎng)景。
- MODE_STREAM:在這種模式下,通過 write 一次次把音頻數(shù)據(jù)寫到 AudioTrack 中。這和平時(shí)通過 write 系統(tǒng)調(diào)用往文件中寫數(shù)據(jù)類似,但這種工作方式每次都需要把數(shù)據(jù)從用戶提供的 Buffer 中拷貝到 AudioTrack 內(nèi)部的 Buffer 中,這在一定程度上會(huì)使引入延時(shí)。為解決這一問題,AudioTrack 就引入了第二種模式。
- MODE_STATIC:這種模式下,在 play 之前只需要把所有數(shù)據(jù)通過一次 write 調(diào)用傳遞到 AudioTrack 中的內(nèi)部緩沖區(qū),后續(xù)就不必再傳遞數(shù)據(jù)了。這種模式適用于像鈴聲這種內(nèi)存占用量較小,延時(shí)要求較高的文件。但它也有一個(gè)缺點(diǎn),就是一次write的數(shù)據(jù)不能太多,否則系統(tǒng)無法分配足夠的內(nèi)存來存儲(chǔ)全部數(shù)據(jù)。
播放聲音可以用 MediaPlayer 和 AudioTrack,兩者都提供了 Java API 供應(yīng)用開發(fā)者使用。雖然都可以播放聲音,但兩者還是有很大的區(qū)別的,其中最大的區(qū)別是MediaPlayer 可以播放多種格式的聲音文件,例如 MP3,AAC,WAV,OGG,MIDI 等。MediaPlayer 會(huì)在 framework 層創(chuàng)建對(duì)應(yīng)的音頻解碼器。而 AudioTrack 只能播放已經(jīng)解碼的 PCM 流,如果對(duì)比支持的文件格式的話則是 AudioTrack 只支持 wav 格式的音頻文件,因?yàn)?wav 格式的音頻文件大部分都是 PCM 流。AudioTrack 不創(chuàng)建解碼器,所以只能播放不需要解碼的 wav 文件。
5.1 音頻流的類型
在 AudioTrack 構(gòu)造函數(shù)中,會(huì)接觸到 AudioManager.STREAM_MUSIC 這個(gè)參數(shù)。它的含義與 Android 系統(tǒng)對(duì)音頻流的管理和分類有關(guān)。
Android 將系統(tǒng)的聲音分為好幾種流類型,下面是幾個(gè)常見的:
STREAM_ALARM:警告聲
STREAM_MUSIC:音樂聲,例如 music 等
STREAM_RING:鈴聲
STREAM_SYSTEM:系統(tǒng)聲音,例如低電提示音,鎖屏音等
STREAM_VOCIE_CALL:通話聲
注意:上面這些類型的劃分和音頻數(shù)據(jù)本身并沒有關(guān)系。例如 MUSIC 和 RING 類型都可以是某首 MP3 歌曲。另外,聲音流類型的選擇沒有固定的標(biāo)準(zhǔn),例如,鈴聲預(yù)覽中的鈴聲可以設(shè)置為MUSIC類型。音頻流類型的劃分和Audio系統(tǒng)對(duì)音頻的管理策略有關(guān)。
5.2 Buffer 分配和 Frame 的概念
在計(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 開始追溯代碼,可以發(fā)現(xiàn)在底層的代碼中有一個(gè)很重要的概念:Frame(幀)。Frame 是一個(gè)單位,用來描述數(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ú)立的單位來表示全部聲道一次采樣的數(shù)據(jù)量,也就引出了 Frame 的概念。Frame 的大小,就是一個(gè)采樣點(diǎn)的字節(jié)數(shù) × 聲道數(shù)。另外,在目前的聲卡驅(qū)動(dòng)程序中,其內(nèi)部緩沖區(qū)也是采用 Frame 作為單位來分配和管理的。
getMinBufSize 會(huì)綜合考慮硬件的情況(諸如是否支持采樣率,硬件本身的延遲情況等)后,得出一個(gè)最小緩沖區(qū)的大小。一般我們分配的緩沖大小會(huì)是它的整數(shù)倍。
5.3 構(gòu)建過程
每一個(gè)音頻流對(duì)應(yīng)著一個(gè) AudioTrack 類的一個(gè)實(shí)例,每個(gè) AudioTrack 會(huì)在創(chuàng)建時(shí)注冊(cè)到 AudioFlinger 中,由 AudioFlinger 把所有的 AudioTrack 進(jìn)行混合(Mixer),然后輸送到 AudioHardware 中進(jìn)行播放,目前 Android 同時(shí)最多可以創(chuàng)建 32 個(gè)音頻流,也就是說,Mixer 最多會(huì)同時(shí)處理 32 個(gè) AudioTrack 的數(shù)據(jù)流。
public class AudioTrackManager { ? ? ... ? ? //音頻流類型 ? ? private static final int mStreamType = AudioManager.STREAM_MUSIC; ? ? //指定采樣率 (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 mMinBufferSize; ? ? //STREAM的意思是由用戶在應(yīng)用程序通過write方式把數(shù)據(jù)一次一次得寫到audiotrack中。這個(gè)和我們?cè)趕ocket中發(fā)送數(shù)據(jù)一樣, ? ? // 應(yīng)用層從某個(gè)地方獲取數(shù)據(jù),例如通過編解碼得到PCM數(shù)據(jù),然后write到audiotrack。 ? ? private static int mMode = AudioTrack.MODE_STREAM; ? ? private void initData() { ? ? ? ? //根據(jù)采樣率,采樣精度,單雙聲道來得到frame的大小。 ? ? ? ? mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);//計(jì)算最小緩沖區(qū) ? ? ? ? //注意,按照數(shù)字音頻的知識(shí),這個(gè)算出來的是一秒鐘buffer的大小。 ? ? ? ? //創(chuàng)建AudioTrack ? ? ? ? mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig, ? ? ? ? ? ? ? ? mAudioFormat, mMinBufferSize, mMode); ? ? } ? ? /** ? ? ?* 啟動(dòng)播放線程 ? ? ?*/ ? ? private void startThread() { ? ? ? ? destroyThread(); ? ? ? ? isStart = true; ? ? ? ? if (mRecordThread == null) { ? ? ? ? ? ? mRecordThread = new Thread(recordRunnable); ? ? ? ? ? ? mRecordThread.start(); ? ? ? ? } ? ? } ? ? /** ? ? ?* 播放線程 ? ? ?*/ ? ? private Runnable recordRunnable = new Runnable() { ? ? ? ? @Override ? ? ? ? public void run() { ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? //設(shè)置線程的優(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; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? //一邊播放一邊寫入語音數(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();//銷毀線程 ? ? ? ? ? ? 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(); ? ? ? ? } ? ? } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android一個(gè)類實(shí)現(xiàn)錄音與播放實(shí)例
- 詳解Android開發(fā)錄音和播放音頻的步驟(動(dòng)態(tài)獲取權(quán)限)
- Android實(shí)現(xiàn)語音播放與錄音功能
- Android編程實(shí)現(xiàn)錄音及保存播放功能的方法【附demo源碼下載】
- android語音即時(shí)通訊之錄音、播放功能實(shí)現(xiàn)代碼
- Android 錄音與播放功能的簡(jiǎn)單實(shí)例
- Android使用MediaRecorder實(shí)現(xiàn)錄音及播放
- Android錄音播放管理工具
- Android實(shí)現(xiàn)自制和播放錄音程序
- Android編程開發(fā)錄音和播放錄音簡(jiǎn)單示例
相關(guān)文章
Android 通過觸摸動(dòng)態(tài)地在屏幕上畫矩形效果
在屏幕上用手指畫出一個(gè)區(qū)域,返回所圈的區(qū)域坐標(biāo)。通過自定義view設(shè)置畫筆及對(duì)應(yīng)參數(shù),在onTouchEvent()回調(diào)函數(shù)里,對(duì)觸摸事件進(jìn)行判斷。畫出矩形圖形,具體實(shí)現(xiàn)代碼大家參考下本文2017-07-07Android中ImageCropper矩形、圓形 裁剪框的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Android中ImageCropper矩形、圓形 裁剪框的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2018-07-07Android 數(shù)據(jù)庫文件存取至儲(chǔ)存卡的方法
這篇文章主要介紹了Android 數(shù)據(jù)庫文件存取至儲(chǔ)存卡的方法的相關(guān)資料,需要的朋友可以參考下2016-03-03