Android?WebRTC?對(duì)?AudioRecord?的使用技術(shù)分享
前言:
AudioRecord 是 Android 基于原始PCM音頻數(shù)據(jù)錄制的類,WebRCT 對(duì)其封裝的代碼位置位于org/webrtc/audio/WebRtcAudioRecord.java,接下來我們學(xué)習(xí)一下 AudioRecord 是如何創(chuàng)建啟動(dò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í)際使用數(shù)據(jù)的代碼在
native層,因此這里創(chuàng)建了一個(gè)Java的direct buffer,而且AudioRecord也有通過ByteBuffer讀數(shù)據(jù)的接口,并且實(shí)際把數(shù)據(jù)復(fù)制到ByteBuffer的代碼也在native層,所以這里使用direct buffer效率會(huì)更高。 ByteBuffer的容量為單次讀取數(shù)據(jù)的大小。Android的數(shù)據(jù)格式是打包格式(packed),在多個(gè)聲道時(shí),同一個(gè)樣點(diǎn)的不同聲道連續(xù)存儲(chǔ)在一起,接著存儲(chǔ)下一個(gè)樣點(diǎn)的不同聲道;一幀就是一個(gè)樣點(diǎn)的所有聲道數(shù)據(jù)的合集,一次讀取的幀數(shù)是10ms的樣點(diǎn)數(shù)(采樣率除以100,樣點(diǎn)個(gè)數(shù)等于采樣率時(shí)對(duì)應(yīng)于1s的數(shù)據(jù),所以除以100就是10ms的數(shù)據(jù));ByteBuffer的容量為幀數(shù)乘聲道數(shù)乘每個(gè)樣點(diǎn)的字節(jié)數(shù)(PCM 16 bit表示每個(gè)樣點(diǎn)為兩個(gè)字節(jié))。- 這里調(diào)用的
nativeCacheDirectBufferAddress JNI函數(shù)會(huì)在native層把ByteBuffer的訪問地址提前保存下來,避免每次讀到音頻數(shù)據(jù)后,還需要調(diào)用接口獲取訪問地址。
創(chuàng)建 AudioRecord對(duì)象,構(gòu)造函數(shù)有很多參數(shù),分析如下:
audioSource:指的是音頻采集模式,默認(rèn)是 VOICE_COMMUNICATION,該模式會(huì)使用硬件AEC(回聲抑制)sampleRate:采樣率channelConfig:聲道數(shù)audioFormat:音頻數(shù)據(jù)格式,這里實(shí)際使用的是 AudioFormat.ENCODING_PCM_16BIT,即PCM 16 bit的數(shù)據(jù)格式。bufferSize:系統(tǒng)創(chuàng)建AudioRecord時(shí)使用的緩沖區(qū)大小,這里使用了兩個(gè)數(shù)值的較大者:通過AudioRecord.getMinBufferSize接口獲取的最小緩沖區(qū)大小的兩倍,讀取數(shù)據(jù)的ByteBuffer的容量。通過注釋我們可以了解到,考慮最小緩沖區(qū)的兩倍是為了確保系統(tǒng)負(fù)載較高的情況下音頻采集仍能平穩(wěn)運(yùn)行,而且這里設(shè)置更大的緩沖區(qū)并不會(huì)增加音頻采集的延遲。
二、啟動(dòng)
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;
? }? 在該方法中,首先啟動(dòng)了 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ù)中。
- 在線程啟動(dòng)的地方,先設(shè)置線程的優(yōu)先級(jí)為URGENT_AUDIO,這里調(diào)用的是Process.setThreadPriority。
- 在一個(gè)循環(huán)中不停地調(diào)用audioRecord.read讀取數(shù)據(jù),把采集到的數(shù)據(jù)讀到ByteBuffer中,然后調(diào)用nativeDataIsRecorded JNI函數(shù)通知native層數(shù)據(jù)已經(jīng)讀到,進(jìn)行下一步處理。
四、停止和銷毀
? 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線程退出。
這里有一點(diǎn)值得一提,keepAlive變量加了volatile關(guān)鍵字進(jìn)行修飾,這是因?yàn)樾薷暮妥x取這個(gè)變量的操作可能發(fā)生在不同的線程,使用volatile關(guān)鍵字進(jìn)行修飾,可以保證修改之后能被立即讀取到。
AudioRecordThread線程退出循環(huán)后,會(huì)調(diào)用audioRecord.stop()停止采集;線程退出之后,會(huì)調(diào)用audioRecord.release()釋放AudioRecord對(duì)象。
? 以上,就是 Android WebRTC 音頻采集 Java 層的大致流程。
到此這篇關(guān)于Android WebRTC 對(duì) AudioRecord 的使用技術(shù)分享的文章就介紹到這了,更多相關(guān)Android WebRTC 對(duì) AudioRecord 的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android快速實(shí)現(xiàn)發(fā)送郵件實(shí)例
本篇文章主要介紹了Android快速實(shí)現(xiàn)發(fā)送郵件實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04
Android開發(fā)之拖動(dòng)條和評(píng)分組件用法分析
這篇文章主要介紹了Android開發(fā)之拖動(dòng)條和評(píng)分組件用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android拖動(dòng)條及評(píng)分組件的布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07
Android編程中Tween動(dòng)畫和Frame動(dòng)畫實(shí)例分析
這篇文章主要介紹了Android編程中Tween動(dòng)畫和Frame動(dòng)畫,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android中Tween動(dòng)畫和Frame動(dòng)畫的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-12-12
Android自定義ListView實(shí)現(xiàn)下拉刷新
這篇文章主要為大家詳細(xì)介紹了Android自定義ListView實(shí)現(xiàn)下拉刷新的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07
Android懸浮球及全局返回功能的實(shí)現(xiàn)示例
這篇文章主要介紹了Android懸浮球及全局返回功能的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04
Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能
這篇文章主要介紹了Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能,需要的朋友可以參考下2017-06-06
Android 基礎(chǔ)入門教程——開發(fā)環(huán)境搭建
這篇文章主要介紹了Android 如何搭建開發(fā)環(huán)境,文中講解非常細(xì)致,幫助大家開始學(xué)習(xí)Android,想要學(xué)習(xí)Android的朋友可以了解下2020-06-06
Android開發(fā)筆記之:Handler Runnable與Thread的區(qū)別詳解
本篇文章是對(duì)在Android中Handler Runnable與Thread的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Android 出現(xiàn):java.lang.NoClassDefFoundError...錯(cuò)誤解決辦法
這篇文章主要介紹了Android 出現(xiàn):Android出現(xiàn):java.lang.NoClassDefFoundError: android/os/PersistableBundle錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-03-03

