Android?WebRTC?對?AudioRecord?的使用技術分享
前言:
AudioRecord 是 Android 基于原始PCM音頻數(shù)據(jù)錄制的類,WebRCT 對其封裝的代碼位置位于org/webrtc/audio/WebRtcAudioRecord.java
,接下來我們學習一下 AudioRecord
是如何創(chuàng)建啟動,讀取音頻采集數(shù)據(jù)以及銷毀等功能的。
一、創(chuàng)建和初始化
private int initRecording(int sampleRate, int channels) { ? ? Logging.d(TAG, "initRecording(sampleRate=" + sampleRate + ", channels=" + channels + ")"); ? ? if (audioRecord != null) { ? ? ? reportWebRtcAudioRecordInitError("InitRecording called twice without StopRecording."); ? ? ? return -1; ? ? } ? ? final int bytesPerFrame = channels * (BITS_PER_SAMPLE / 8); ? ? final int framesPerBuffer = sampleRate / BUFFERS_PER_SECOND; ? ? byteBuffer = ByteBuffer.allocateDirect(bytesPerFrame * framesPerBuffer); ? ? Logging.d(TAG, "byteBuffer.capacity: " + byteBuffer.capacity()); ? ? emptyBytes = new byte[byteBuffer.capacity()]; ? ? // Rather than passing the ByteBuffer with every callback (requiring ? ? // the potentially expensive GetDirectBufferAddress) we simply have the ? ? // the native class cache the address to the memory once. ? ? nativeCacheDirectBufferAddress(byteBuffer, nativeAudioRecord); ? ? // Get the minimum buffer size required for the successful creation of ? ? // an AudioRecord object, in byte units. ? ? // Note that this size doesn't guarantee a smooth recording under load. ? ? final int channelConfig = channelCountToConfiguration(channels); ? ? int minBufferSize = ? ? ? ? AudioRecord.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT); ? ? if (minBufferSize == AudioRecord.ERROR || minBufferSize == AudioRecord.ERROR_BAD_VALUE) { ? ? ? reportWebRtcAudioRecordInitError("AudioRecord.getMinBufferSize failed: " + minBufferSize); ? ? ? return -1; ? ? } ? ? Logging.d(TAG, "AudioRecord.getMinBufferSize: " + minBufferSize); ? ? // Use a larger buffer size than the minimum required when creating the ? ? // AudioRecord instance to ensure smooth recording under load. It has been ? ? // verified that it does not increase the actual recording latency. ? ? int bufferSizeInBytes = Math.max(BUFFER_SIZE_FACTOR * minBufferSize, byteBuffer.capacity()); ? ? Logging.d(TAG, "bufferSizeInBytes: " + bufferSizeInBytes); ? ? try { ? ? ? audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, ? ? ? ? ? AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes); ? ? } catch (IllegalArgumentException e) { ? ? ? reportWebRtcAudioRecordInitError("AudioRecord ctor error: " + e.getMessage()); ? ? ? releaseAudioResources(); ? ? ? return -1; ? ? } ? ? if (audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { ? ? ? reportWebRtcAudioRecordInitError("Failed to create a new AudioRecord instance"); ? ? ? releaseAudioResources(); ? ? ? return -1; ? ? } ? ? if (effects != null) { ? ? ? effects.enable(audioRecord.getAudioSessionId()); ? ? } ? ? logMainParameters(); ? ? logMainParametersExtended(); ? ? return framesPerBuffer; ? }
在初始化的方法中,主要做了兩件事。
創(chuàng)建緩沖區(qū):
- 由于實際使用數(shù)據(jù)的代碼在
native
層,因此這里創(chuàng)建了一個Java的direct buffer,而且AudioRecord也有通過ByteBuffer讀數(shù)據(jù)的接口,并且實際把數(shù)據(jù)復制到ByteBuffer
的代碼也在native層,所以這里使用direct buffer效率會更高。 ByteBuffer
的容量為單次讀取數(shù)據(jù)的大小。Android的數(shù)據(jù)格式是打包格式(packed),在多個聲道時,同一個樣點的不同聲道連續(xù)存儲在一起,接著存儲下一個樣點的不同聲道;一幀就是一個樣點的所有聲道數(shù)據(jù)的合集,一次讀取的幀數(shù)是10ms的樣點數(shù)(采樣率除以100,樣點個數(shù)等于采樣率時對應于1s的數(shù)據(jù),所以除以100就是10ms的數(shù)據(jù));ByteBuffer的容量為幀數(shù)乘聲道數(shù)乘每個樣點的字節(jié)數(shù)(PCM 16 bit表示每個樣點為兩個字節(jié))。- 這里調(diào)用的
nativeCacheDirectBufferAddress JNI
函數(shù)會在native層把ByteBuffer的訪問地址提前保存下來,避免每次讀到音頻數(shù)據(jù)后,還需要調(diào)用接口獲取訪問地址。
創(chuàng)建 AudioRecord對象,構造函數(shù)有很多參數(shù),分析如下:
audioSource
:指的是音頻采集模式,默認是 VOICE_COMMUNICATION,該模式會使用硬件AEC(回聲抑制)sampleRate
:采樣率channelConfig
:聲道數(shù)audioFormat
:音頻數(shù)據(jù)格式,這里實際使用的是 AudioFormat.ENCODING_PCM_16BIT,即PCM 16 bit的數(shù)據(jù)格式。bufferSize
:系統(tǒng)創(chuàng)建AudioRecord
時使用的緩沖區(qū)大小,這里使用了兩個數(shù)值的較大者:通過AudioRecord.getMinBufferSize接口獲取的最小緩沖區(qū)大小的兩倍,讀取數(shù)據(jù)的ByteBuffer的容量。通過注釋我們可以了解到,考慮最小緩沖區(qū)的兩倍是為了確保系統(tǒng)負載較高的情況下音頻采集仍能平穩(wěn)運行,而且這里設置更大的緩沖區(qū)并不會增加音頻采集的延遲。
二、啟動
private boolean startRecording() { ? ? Logging.d(TAG, "startRecording"); ? ? assertTrue(audioRecord != null); ? ? assertTrue(audioThread == null); ? ? try { ? ? ? audioRecord.startRecording(); ? ? } catch (IllegalStateException e) { ? ? ? reportWebRtcAudioRecordStartError(AudioRecordStartErrorCode.AUDIO_RECORD_START_EXCEPTION, ? ? ? ? ? "AudioRecord.startRecording failed: " + e.getMessage()); ? ? ? return false; ? ? } ? ? if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { ? ? ? reportWebRtcAudioRecordStartError( ? ? ? ? ? AudioRecordStartErrorCode.AUDIO_RECORD_START_STATE_MISMATCH, ? ? ? ? ? "AudioRecord.startRecording failed - incorrect state :" ? ? ? ? ? + audioRecord.getRecordingState()); ? ? ? return false; ? ? } ? ? audioThread = new AudioRecordThread("AudioRecordJavaThread"); ? ? audioThread.start(); ? ? return true; ? }
? 在該方法中,首先啟動了 audioRecord
,接著判斷了讀取線程事都正在錄制中。
三、讀數(shù)據(jù)
?private class AudioRecordThread extends Thread { ? ? private volatile boolean keepAlive = true; ? ? public AudioRecordThread(String name) { ? ? ? super(name); ? ? } ? ? // TODO(titovartem) make correct fix during webrtc:9175 ? ? @SuppressWarnings("ByteBufferBackingArray") ? ? @Override ? ? public void run() { ? ? ? Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); ? ? ? Logging.d(TAG, "AudioRecordThread" + WebRtcAudioUtils.getThreadInfo()); ? ? ? assertTrue(audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING); ? ? ? long lastTime = System.nanoTime(); ? ? ? while (keepAlive) { ? ? ? ? int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity()); ? ? ? ? if (bytesRead == byteBuffer.capacity()) { ? ? ? ? ? if (microphoneMute) { ? ? ? ? ? ? byteBuffer.clear(); ? ? ? ? ? ? byteBuffer.put(emptyBytes); ? ? ? ? ? } ? ? ? ? ? // It's possible we've been shut down during the read, and stopRecording() tried and ? ? ? ? ? // failed to join this thread. To be a bit safer, try to avoid calling any native methods ? ? ? ? ? // in case they've been unregistered after stopRecording() returned. ? ? ? ? ? if (keepAlive) { ? ? ? ? ? ? nativeDataIsRecorded(bytesRead, nativeAudioRecord); ? ? ? ? ? } ? ? ? ? ? if (audioSamplesReadyCallback != null) { ? ? ? ? ? ? // Copy the entire byte buffer array. ?Assume that the start of the byteBuffer is ? ? ? ? ? ? // at index 0. ? ? ? ? ? ? byte[] data = Arrays.copyOf(byteBuffer.array(), byteBuffer.capacity()); ? ? ? ? ? ? audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady( ? ? ? ? ? ? ? ? new AudioSamples(audioRecord, data)); ? ? ? ? ? } ? ? ? ? } else { ? ? ? ? ? String errorMessage = "AudioRecord.read failed: " + bytesRead; ? ? ? ? ? Logging.e(TAG, errorMessage); ? ? ? ? ? if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) { ? ? ? ? ? ? keepAlive = false; ? ? ? ? ? ? reportWebRtcAudioRecordError(errorMessage); ? ? ? ? ? } ? ? ? ? } ? ? ? ? if (DEBUG) { ? ? ? ? ? long nowTime = System.nanoTime(); ? ? ? ? ? long durationInMs = TimeUnit.NANOSECONDS.toMillis((nowTime - lastTime)); ? ? ? ? ? lastTime = nowTime; ? ? ? ? ? Logging.d(TAG, "bytesRead[" + durationInMs + "] " + bytesRead); ? ? ? ? } ? ? ? } ? ? ? try { ? ? ? ? if (audioRecord != null) { ? ? ? ? ? audioRecord.stop(); ? ? ? ? } ? ? ? } catch (IllegalStateException e) { ? ? ? ? Logging.e(TAG, "AudioRecord.stop failed: " + e.getMessage()); ? ? ? } ? ? } ? ? // Stops the inner thread loop and also calls AudioRecord.stop(). ? ? // Does not block the calling thread. ? ? public void stopThread() { ? ? ? Logging.d(TAG, "stopThread"); ? ? ? keepAlive = false; ? ? } ? }
? 從 AudioRecord
去數(shù)據(jù)的邏輯在 AudioRecordThread
線程的 Run函數(shù)中。
- 在線程啟動的地方,先設置線程的優(yōu)先級為URGENT_AUDIO,這里調(diào)用的是Process.setThreadPriority。
- 在一個循環(huán)中不停地調(diào)用audioRecord.read讀取數(shù)據(jù),把采集到的數(shù)據(jù)讀到ByteBuffer中,然后調(diào)用nativeDataIsRecorded JNI函數(shù)通知native層數(shù)據(jù)已經(jīng)讀到,進行下一步處理。
四、停止和銷毀
? private boolean stopRecording() { ? ? Logging.d(TAG, "stopRecording"); ? ? assertTrue(audioThread != null); ? ? audioThread.stopThread(); ? ? if (!ThreadUtils.joinUninterruptibly(audioThread, AUDIO_RECORD_THREAD_JOIN_TIMEOUT_MS)) { ? ? ? Logging.e(TAG, "Join of AudioRecordJavaThread timed out"); ? ? ? WebRtcAudioUtils.logAudioState(TAG); ? ? } ? ? audioThread = null; ? ? if (effects != null) { ? ? ? effects.release(); ? ? } ? ? releaseAudioResources(); ? ? return true; ? }
? 可以看到,這里首先把AudioRecordThread
讀數(shù)據(jù)循環(huán)的keepAlive條件置為false,接著調(diào)用ThreadUtils.joinUninterruptibly等待AudioRecordThread
線程退出。
這里有一點值得一提,keepAlive
變量加了volatile關鍵字進行修飾,這是因為修改和讀取這個變量的操作可能發(fā)生在不同的線程,使用volatile關鍵字進行修飾,可以保證修改之后能被立即讀取到。
AudioRecordThread
線程退出循環(huán)后,會調(diào)用audioRecord.stop()
停止采集;線程退出之后,會調(diào)用audioRecord.release()
釋放AudioRecord
對象。
? 以上,就是 Android WebRTC 音頻采集 Java 層的大致流程。
到此這篇關于Android WebRTC 對 AudioRecord 的使用技術分享的文章就介紹到這了,更多相關Android WebRTC 對 AudioRecord 的使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Flutter開發(fā)之Shortcuts快捷鍵組件的用法詳解
在桌面端的開發(fā)中,鍵盤快捷鍵是非常常見而必要的,F(xiàn)lutter?既然可以開發(fā)桌面端應用,那必然要提供自定義快捷鍵,所以本文就來和大家講講Shortcuts組件的簡單使用吧2023-05-05Android開發(fā)中Launcher3常見默認配置修改方法總結
這篇文章主要介紹了Android開發(fā)中Launcher3常見默認配置修改方法,結合實例形式分析了Android Launcher3的功能與配置修改相關操作技巧,需要的朋友可以參考下2017-11-11android之視頻播放系統(tǒng)VideoView和自定義VideoView控件的應用
這篇文章主要介紹了android之視頻播放系統(tǒng)VideoView和自定義VideoView控件的應用,需要的朋友可以參考下2017-03-03Android開發(fā)listview選中高亮簡單實現(xiàn)代碼分享
這篇文章主要介紹了Android開發(fā)listview選中高亮簡單實現(xiàn)代碼分享,具有一定借鑒價值,需要的朋友可以參考下2018-01-01