欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android實(shí)現(xiàn)音頻錄音與播放

 更新時(shí)間:2022年08月08日 17:19:10   作者:初壹十五a  
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)音頻錄音與播放,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

本文介紹音頻的采集、編碼、生成文件、轉(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í)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Android 通過觸摸動(dòng)態(tài)地在屏幕上畫矩形效果

    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-07
  • Android控件View的文字周圍添加圖標(biāo)

    Android控件View的文字周圍添加圖標(biāo)

    這篇文章主要為大家詳細(xì)介紹了Android控件View的文字周圍添加圖標(biāo),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-03-03
  • Android中ImageCropper矩形、圓形 裁剪框的實(shí)現(xiàn)方法

    Android中ImageCropper矩形、圓形 裁剪框的實(shí)現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于Android中ImageCropper矩形、圓形 裁剪框的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2018-07-07
  • Android 數(shù)據(jù)庫文件存取至儲(chǔ)存卡的方法

    Android 數(shù)據(jù)庫文件存取至儲(chǔ)存卡的方法

    這篇文章主要介紹了Android 數(shù)據(jù)庫文件存取至儲(chǔ)存卡的方法的相關(guān)資料,需要的朋友可以參考下
    2016-03-03
  • Android開發(fā)入門之Appwidget用法分析

    Android開發(fā)入門之Appwidget用法分析

    這篇文章主要介紹了Android開發(fā)入門之Appwidget用法,較為詳細(xì)的分析了App Widget的概念、功能、創(chuàng)建、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2016-07-07
  • Kotlin對(duì)象比較注意點(diǎn)示例詳解

    Kotlin對(duì)象比較注意點(diǎn)示例詳解

    你知道在kotlin中,如何來比較對(duì)象相等嗎?下面這篇文章主要給大家介紹了關(guān)于Kotlin對(duì)象比較注意點(diǎn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • Android 表格布局TableLayout示例詳解

    Android 表格布局TableLayout示例詳解

    本文主要介紹Android TableLayout布局,這里整理了TableLayout的資料,并附示例代碼和實(shí)現(xiàn)效果圖,有興趣的小伙伴可以參考下
    2016-08-08
  • Flutter系列重學(xué)Container示例詳解

    Flutter系列重學(xué)Container示例詳解

    這篇文章主要為大家介紹了Flutter系列重學(xué)Container示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Kotlin擴(kuò)展函數(shù)超詳細(xì)介紹

    Kotlin擴(kuò)展函數(shù)超詳細(xì)介紹

    Kotlin?可以為一個(gè)不能修改的或來自第三方庫中的類編寫一個(gè)新的函數(shù)。?這個(gè)新增的函數(shù)就像那個(gè)原始類本來就有的函數(shù)一樣,可以用普通的方法調(diào)用,這種機(jī)制的函數(shù)稱為擴(kuò)展函數(shù)
    2022-09-09
  • Android中多行文本末尾添加圖片排版問題的解決方法

    Android中多行文本末尾添加圖片排版問題的解決方法

    這篇文章主要給大家介紹了關(guān)于Android中多行文本末尾添加圖片排版問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07

最新評(píng)論