A音頻
A音頻在AndroidØ版本引入了全新的Android C API。它是專為那些需要低延遲的高性能音頻應用。應用程序通過讀取和寫入數據流與A音頻通信。
注: 這是A音頻庫的預覽版本。該API可能在將來的版本后向兼容的方式發生變化。不建議在生產中使用。該A音頻API是由設計最小的,它不執行這些功能:
- 音頻設備枚舉
- 音頻端點之間自動路由
- 文件I / O
- 壓縮音頻的解碼
- 所有輸入的自動演示/流在一個單獨的回調。
音頻流
A音頻應用程式和Android設備上的音頻輸入和輸出之間移動音頻數據。你的應用程序通過讀取和寫入進出數據的音頻流,通過結構AAudioStream表示。讀/寫呼叫可以被阻塞或非阻塞。
流由以下定義:
- 的音頻 設備,即源或匯的流中的數據。
- 所述共享模式,其確定一個流是否具有可能另外多個流之間共享的音頻裝置的獨占訪問。
- 該格式的流中的音頻數據。
音頻設備
每個流被連接到單個音頻設備。
音頻設備是硬件接口或虛擬端點充當源或匯用於數字音頻數據的連續流。不要混淆的音頻設備 與(一個內置的麥克風或藍牙耳機)的Android設備運行你的應用程序(電話或觀看)。
您可以使用該AudioManager
方法getDevices()
來發現您可以在Android設備上的音頻設備。該方法返回有關信息type
的每個設備。
每個音頻設備具有Android設備上的唯一ID。您可以使用ID的音頻流綁定到特定的音頻設備。然而,在大多數情況下,你可以讓A音頻選擇默認的主設備,而不是你自己指定一個。
附連到流音頻設備確定該流是否為輸入或輸出。甲流只能在一個方向上移動數據。當你定義一個流您還可以設置它的方向。當你打開一個流的Android檢查,以確保音頻設備和流方向一致。
共享模式
甲流有一個共享模式:
AAUDIO_SHARING_MODE_EXCLUSIVE
意味着流具有其音頻設備的獨占訪問; 該裝置不能被任何其他音頻流被使用。如果音頻設備已在使用,它可能無法為流具有獨占訪問。獨家流可能有更低的延遲,但他們也更容易獲得斷開。您應該盡快關閉獨家流,你不再需要它們,以便其他應用程序可以訪問該設備。獨家流提供盡可能低的延遲。AAUDIO_SHARING_MODE_SHARED
允許A音頻混合音頻。A音頻混合所有分配到同一設備的共享流。
當你創建一個流可以明確設置共享模式。默認情況下,共享模式SHARED
。
音頻格式
通過流傳遞的數據有通常的數字音頻屬性,當你定義一個流必須指定。這些措施如下:
- 樣本格式
- 每幀樣本
- 采樣率
A音頻允許這些樣品格式:
aaudio_format_t | C數據類型 | 筆記 |
AAUDIO__FORMAT_PCM_I16 | int16_t | 常見的16位樣本,Q0.15格式 |
AAUDIO_FORMAT_PCM_FLOAT | 浮動 | -1.0到+1.0 |
A音頻可能對自己進行采樣轉換。例如,如果一個應用程序被寫入FLOAT數據,但HAL使用PCM_I16,A音頻會自動轉換樣本。轉換可以在任一方向發生。如果您的應用程序處理音頻輸入,這是明智的驗證輸入格式,並准備在必要時轉換數據,如下例所示:
aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
convertFloatToPcm16(...)
}
創建音頻流
在A音頻庫遵循制造商的設計模式,並提供AAudioStreamBuilder。
- 創建AAudioStreamBuilder:
AAudioStreamBuilder *builder;
aaudio_result_t result = AAudio_createStreamBuilder(&builder); - 設置在構建器中的音頻流的配置,使用對應於所述流參數的助洗劑功能。這些可選設置功能:
AAudioStreamBuilder_setDeviceId(builder, deviceId);
AAudioStreamBuilder_setDirection(builder, direction);
AAudioStreamBuilder_setSharingMode(builder, mode);
AAudioStreamBuilder_setSampleRate(builder, sampleRate);
AAudioStreamBuilder_setSamplesPerFrame(builder, spf);
AAudioStreamBuilder_setFormat(builder, format);
AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);注意,這些方法不報告錯誤,如一個未定義的常量或值超出范圍。
如果不指定設備ID,默認是主要的輸出設備。如果不指定流方向上,默認是輸出流。對於所有其它參數,你可以明確地設定一個值,或者讓系統不指定所有參數或設置它來分配最佳值
AAUDIO_UNSPECIFIED
。為了安全起見,檢查音頻流的狀態在創建后,按照步驟4所說明的那樣。
- 當AAudioStreamBuilder配置,用它來創建流:
AAudioStream *stream;
result = AAudioStreamBuilder_openStream(builder, &stream); - 創建流后,驗證其配置。如果您指定每幀的采樣格式,采樣率,或樣品,他們將不會改變。然而,共享模式和緩存容量可能會隨流的音頻設備的能力,並在其上正在運行的Android設備改變(無論是否設置)。由於良好的防御性編程的問題,你在使用前應檢查流的配置。有函數來獲取對應於每個建設者設置流設置:
- 您可以保存生成器,它在將來重復使用,使更多的流。但是,如果你不打算使用它了,應該將其刪除。
AAudioStreamBuilder_delete(builder);
使用音頻流
狀態轉變
一個A音頻流通常是由5種穩定狀態中的一種(錯誤狀態,斷開,在該部分的端部所描述的):
- 打開
- 入門
- 已暫停
- 酡
- 停止
數據只有當流處於啟動狀態流過流。移動狀態之間的流,使用該請求的狀態轉變的一個功能:
aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);
請注意,您只能請求暫停或平齊的輸出流:
這些功能是異步的,並且狀態變化不會立即發生。當您請求的狀態變化,流使相應的過渡狀態中的一種:
- 開始
- 暫停
- 法拉盛
- 停止
- 閉幕
下面的狀態圖顯示了穩定狀態為圓角矩形,而過渡狀態為點的矩形。雖然它沒有顯示,你可以調用close()
從任何狀態
A音頻不提供回調來提醒您狀態的變化。一個特殊的功能, AAudioStream_waitForStateChange(stream, inputState, nextState, timeout)
可以用來等待的狀態變化。
該功能不檢測自身的狀態變化,而不會等待一個特定的狀態。它等待,直到當前的狀態是不同的比inputState
,可以指定。
例如,請求暫停后,流應立即進入過渡狀態暫停,並到達暫停狀態晚些時候-盡管也不能保證它會的。既然你不能等待暫停狀態,請waitForStateChange()
等待比其他暫停任何狀態。以下是如何這樣做了:
aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);
如果流的狀態不是暫停(中inputState
,我們假設是在調用時的當前狀態),該函數立即返回。否則,它會阻止,直到狀態不再暫停或超時期滿。當該函數返回,參數nextState
示出了流的當前狀態。
您可以呼叫請求后開始使用同樣的技術,停止或沖洗,使用相應的過渡狀態作為inputState。
讀取和寫入音頻流
流啟動后,你可以閱讀或使用該函數寫它 AAudioStream_read(stream, buffer, numFrames, timeoutNanos)
和 AAudioStream_write(stream, buffer, numFrames, timeoutNanos)
。
對於阻擋讀或寫幀指定數目的傳送,設置timeoutNanos大於零。對於非阻塞調用,設置timeoutNanos為零。在這種情況下,結果是轉移的幀的實際數目。
當你讀輸入,您應該驗證讀取幀的正確數目。如果不是,緩沖區可能含有未知的數據,可能會導致音頻故障。你可以墊用零緩沖,以創建一個無聲輟學:
aaudio_result_t result =
AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
// Error!
}
if (result != numFrames) {
// pad the buffer with zeros
memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}
您可以通過將數據寫入或沉默到它開始流之前素流的緩沖區。這必須與timeoutNanos非阻塞調用設置為零來完成。
在緩沖區中的數據必須通過返回的數據格式相匹配AAudioStream_getDataFormat()
。
關閉音頻流
當您使用的是流完成后,將其關閉:
AAudioStream_close(stream);
斷開音頻流
音頻流可以在任何時候斷開連接,如果這些事件之一發生:
- 相關的音頻設備不再連接。
- 內部會發生錯誤。
- 音頻裝置不再是主音頻設備。
當流被斷開時,它具有狀態“斷開連接”和執行寫入的任何企圖()或其他函數返回AAUDIO_ERROR_DISCONNECTED
。當流被切斷,所有你能做的就是關閉它。
優化性能
您可以通過調整其內部的緩沖區,並通過使用特殊的高優先級的線程優化的音頻應用程序的性能。
調整緩沖區,以盡量減少延遲
A音頻進出它保持,一個用於每個音頻設備的內部緩沖器的數據傳遞。
注意:不要混淆A音頻的內部緩沖區與A音頻流的緩沖參數讀寫功能。該緩沖區的容量是數據的總量緩沖器可容納。您可以撥打 AAudioStreamBuilder_setBufferCapacityInFrames()
設置的能力。該方法限制了可分配給最大值,該設備允許的容量。使用 AAudioStream_getBufferCapacityInFrames()
驗證緩存的實際容量。
一個應用程序沒有使用緩沖區中的全部能力。A音頻填補了緩沖高達尺寸,你可以設置。一緩沖區的大小可以不超過它的容量大,並且它往往是更小的。通過控制緩沖大小你確定需要填充它脈沖串的數量,從而控制延遲。使用方法AAudioStreamBuilder_setBufferSizeInFrames()
和AAudioStreamBuilder_getBufferSizeInFrames()
使用緩沖區大小的工作。
當應用程序播放音頻輸出,直到寫操作完成寫入緩沖區和塊。A音頻從以離散的脈沖串緩沖區讀取。每個突發包含音頻幀的倍數數目,通常是小於正被讀取的緩沖區的大小。該系統控制突發大小和速率,這些性能是通過音頻設備的電路典型地支配。盡管你不能改變突發或突發速率的大小,就可以根據其包含的突發的數目設置的內部緩沖器的大小。一般情況下,你會得到最低的延遲,如果你的AAudioStream的緩沖區大小是報告的突發大小的倍數。
優化緩沖區大小的一種方法是先從大的緩沖區,並逐步降低,直到欠載開始,然后輕推它回來了。或者,你可以用一個小的緩沖區開始,如果產生欠載運行,增加緩沖區大小,直到輸出干凈再流動。
這個過程可以發生得非常快,用戶播放第一個聲音可能之前。您可能需要執行初始緩沖大小首先,使用沉默,使用戶不會聽到任何聲音的毛刺。系統性能可能隨時間(例如,用戶可能關閉飛行模式)而改變。由於緩沖調整增加了很少的開銷,你的應用程序可以在應用程序讀取或向流寫入的數據,連續做。
這里是一個緩沖優化循環的一個例子:
int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);
int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);
while (go) {
result = writeSomeData();
if (result < 0) break;
// Are we getting underruns?
if (bufferSize < bufferCapacity) {
int32_t underrunCount = AAudioStream_getXRunCount(stream);
if (underrunCount > previousUnderrunCount) {
previousUnderrunCount = underrunCount;
// Try increasing the buffer size by one burst
bufferSize += framesPerBurst;
bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
}
}
}
沒有優勢,使用這種技術來優化輸入流的緩沖區大小。輸入流運行盡可能快,試圖緩沖數據的量保持在最低限度,然后填滿時,應用程序被搶占。
使用高優先級的回調
如果您的應用程序讀取或從一個普通的線程寫入音頻數據,它可能被搶占或經歷定時抖動。這可能會導致音頻故障。使用更大的緩沖區可能防范此類故障,但一個大的緩沖區也引入了較長的音頻延遲。對於需要低延遲的應用,音頻流可以使用異步回調函數來傳輸數據和從您的應用程序。A音頻執行具有更好的性能,更高優先級的線程回調。
回調函數的原型如下:
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames);
使用流房屋登記回調:
AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);
在最簡單的情況下,流周期性地執行回調函數來獲取數據以其下一突發。
回調函數不應該進行讀或調用它的流寫入。如果回調屬於一個輸入流,您的代碼應過程,是在audioData緩沖液(指定為第三個參數)提供的數據。如果回調屬於輸出流,你的代碼應該把數據放入緩沖區。
例如,你可以使用一個回調,不斷產生這樣的正弦波輸出:
aaudio_data_callback_result_t myCallback(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames) {
int64_t timeout = 0;
// Write samples directly into the audioData array.
generateSineWave(static_cast<float *>(audioData), numFrames);
return AAUDIO_CALLABCK_RESULT_CONTINUE;
}
它可以處理使用A音頻不止一個流。可以使用一個流作為主,並傳遞指向其他流中的用戶數據。注冊為主控流回調。然后在其他流使用非阻塞I / O。下面是通過一個輸入流,以輸出流的往返回調的一個例子。主呼叫流是輸出流。輸入流被包括在用戶數據。
回調做一個無阻塞從將數據放置到輸出流的緩沖器中的輸入流中讀取:
aaudio_data_callback_result_t myCallback(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames) {
AAudioStream *inputStream = (AAudioStream *) userData;
int64_t timeout = 0;
aaudio_result_t result =
AAudioStream_read(inputStream, audioData, numFrames, timeout);
if (result == numFrames)
return AAUDIO_CALLABCK_RESULT_CONTINUE;
if (result >= 0) {
memset(static_cast<sample_type*>(userData) + result * samplesPerFrame, 0,
sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
return AAUDIO_CALLBACK_RESULT_STOP;
}
注意,在該示例中,假設在輸入和輸出流具有相同數量的信道,格式和采樣率的。該流的格式可以不匹配的 - 只要代碼正確處理翻譯。
設置性能模式
每個AAudioStream有一個性能模式這對您的應用程序的行為有很大的影響。有三種模式:
AAUDIO_PERFORMANCE_MODE_NONE
是默認模式。它使用的是平衡延遲和功耗節省一個基本流。AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
使用較小的緩沖器和用於減少延遲優化的數據路徑。AAUDIO_PERFORMANCE_MODE_POWER_SAVING
使用更大的內部緩沖器和該折衷的等待時間更低的功率的數據路徑。
你可以通過調用選擇性能模式setPerformanceMode() ,並通過調用發現當前模式getPerformanceMode() 。
如果低延遲比你的應用省電更重要的是,使用AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
。這是一個應用程序,是非常互動,如游戲或鍵盤合成有用。
如果省電比你的應用程序低延遲更加重要,使用AAUDIO_PERFORMANCE_MODE_POWER_SAVING
。這是典型的為回放先前生成的音樂應用,如流式音頻或MIDI文件播放器。
在當前版本A音頻的,以達到您必須使用盡可能低的延遲AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
高優先級的回調以及性能模式。按照這個例子:
// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);
線程安全
該A音頻API沒有完全線程安全的。你不能同時從多個線程同時調用一些的A音頻功能。這是因為A音頻避免使用互斥,這可能會導致線程搶占和毛刺。
為了安全起見,不要打電話AAudioStream_waitForStateChange()
或讀或從兩個不同的線程寫入相同的流。同樣,不要在讀取或在另一個線程寫入它在一個線程關閉流。
返回流設置,如電話AAudioStream_getSampleRate()
和AAudioStream_getSamplesPerFrame()
,是線程安全的。
這些電話也是線程安全的:
AAudio_convert*ToText()
AAudio_createStreamBuilder()
AAudioStream_get*()
除了AAudioStream_getTimestamp()
代碼示例
兩個小A音頻演示的應用程序都可以在我們的GitHub頁面:
Hello-Audio
產生正弦波並回放的音頻。Echo
演示如何實現的輸入/輸出音頻往返循環。它使用用於同步兩個流的回調函數,並且執行連續緩沖器調諧。
已知的問題
- 音頻延遲很高阻止寫(),因為在AndroidØDP2釋放不使用快車道。使用回調以獲取更低的延遲。
AAUDIO_SHARING_MODE_EXCLUSIVE
不支持。使用AAUDIO_SHARING_MODE_SHARED
來代替。