音頻延遲時間
本頁內容
視頻
音頻延遲時間:緩衝區大小
視頻
在 Android 上打造出色的多媒體體驗
延遲時間是指信號在系統中傳輸所需的時間。下面是與音頻應用相關的常見類型的延遲時間:
- 音頻輸出時間延遲是指音頻樣本由應用生成到通過耳機插孔或內置揚聲器播放之間的時間。
- 音頻輸入延遲時間是指音頻信號由設備音頻輸入(如麥克風)接收到相同音頻數據可被應用使用的時間。
-
往返延遲時間是指輸入延遲時間、應用處理時間和輸出延遲時間的總和。
- 觸摸延遲時間是指用戶觸摸屏幕與觸摸事件被應用接收之間的時間。
- 預熱延遲時間是指啓動音頻管道、數據第一次在緩衝區加入隊列所需的時間。
本頁面將介紹如何在開發您的音頻應用時保證低輸入和輸出延遲時間以及如何避免出現預熱延遲時間。
先決條件
僅在使用 OpenSL ES™ API 的 Android 實現和 Android NDK 時,才支持低延遲時間音頻。
- 閱讀 OpenSL ES 文檔。
- 下載並安裝 Android NDK。
測量延遲時間
很難單獨測量音頻輸入和輸出延遲時間,因爲需要準確地瞭解第一個樣本何時傳送入音頻路徑(儘管可以使用光檢測電路和示波器完成)。 如果您瞭解往返音頻延遲時間,則可以使用一般經驗法則:音頻輸入(和輸出)延遲時間是經過無信號處理路徑的往返音頻延遲時間的一半。
往返音頻延遲時間根據設備型號和 Android 版本號的不同而大不相同。 您可以通過閱讀已發佈的測量值大略瞭解 Nexus 設備的往返延遲時間。
您可以通過創建應用測量往返音頻延遲時間,該應用能夠生成音頻信號、偵聽此音頻信號和測量發送與接收信號之間的時間。或者,您也可以安裝此延遲時間測試應用。 此應用使用拉森測試執行往返延遲時間測試。 您也可以查看延遲時間測試應用的源代碼。
由於最低延遲時間是在信號處理最少的音頻路徑上獲得的,您可能還想使用迴環音頻適配器,它讓測試能夠通過耳麥連接器運行。
最大程度減少延遲時間的最佳做法
驗證音頻性能
Android 兼容性定義文檔 (CDD) 枚舉了兼容 Android 設備的硬件和軟件要求。請參閱 Android 兼容性瞭解與整體兼容性計劃有關的詳細信息,參閱 CDD 瞭解實際的 CDD 文檔。
在 CDD 中,往返延遲時間被指定爲 20 毫秒或更低(而樂師通常需要 10 毫秒)。 這是因爲 20 毫秒可以實現一些重要的用例。
當前沒有 API 可以在運行時確定 Android 設備上通過任何路徑的音頻延遲時間。 不過,您可以使用下列硬件功能標記了解設備是否能爲延遲時間提供任何保證:
android.hardware.audio.low_latency
指示 45 毫秒或更短的持續輸出延遲時間。android.hardware.audio.pro
指示 20 毫秒或更短的持續往返延遲時間。
報告這些標記的標準在 CDD 的 5.6 音頻延遲時間和 5.10 專業音頻部分中定義。
以下是如何在 Java 中檢查這些功能:
boolean hasLowLatencyFeature = getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY); boolean hasProFeature = getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO);
對於各音頻功能的關係,android.hardware.audio.low_latency
功能是 android.hardware.audio.pro
的先決條件。 設備可以實現 android.hardware.audio.low_latency
而不能實現 android.hardware.audio.pro
,但反之則不然。
不作有關音頻性能的假設
請注意有助於避免延遲時間問題的下列假設:
- 不要假設移動設備中使用的揚聲器和麥克風通常擁有良好的音效。 它們的體積較小,通常音效較差,所以增加信號處理來提高音質。 此信號處理會引起延遲。
- 不要假設您的輸入和輸出回調是同步的。對於同步輸入和輸出,將爲每一側使用單獨的緩衝區隊列完成處理程序。 即使兩側均採用相同的採樣率,也無法保證這些回調的相對順序或音頻時鐘的同步。 您的應用應當在適當同步緩衝區的情況下緩衝數據。
- 不要假設實際採樣率與名義採樣率完全一致。例如,如果名義採樣率是 48,000 Hz,則正常情況下,音頻時鐘會使用與操作系統
CLOCK_MONOTONIC
稍微不同的採樣率計時。 這是因爲音頻和系統時鐘由不同的晶體制成。 - 不要假設實際回放樣本率與實際捕獲採樣率完全一致,特別是端點在單獨的路徑上時。 例如,如果您正以 48,000 Hz 的名義採樣率從設備上的麥克風捕獲數據,並以 48,000 Hz 的名義採樣率在 USB 上播放音頻,實際採樣率很可能會彼此稍有不同。
潛在獨立的音頻時鐘的一個結果是需要異步採樣率轉換。 異步採樣率轉換的簡單(儘管音頻質量不理想)方法是根據需要在接近過零點的位置重複或減少樣本。 也可以進行更復雜的轉換。
最大程度減少輸入延遲時間
此部分會提供建議,幫助您在使用內置麥克風或外部耳麥麥克風錄音時減少音頻輸入延遲時間。
- 如果您的應用要監控輸入,建議您的用戶使用耳麥(例如,通過在第一次運行時顯示最好使用耳機屏幕)。 請注意,僅使用耳麥無法保證儘可能最低的延遲時間。 您可能需要執行其他步驟從音頻路徑中移除任何不需要的信號處理(例如,在錄音時通過使用
VOICE_RECOGNITION
預設值)。 - 準備好處理由 PROPERTY_OUTPUT_SAMPLE_RATE 的 getProperty(String) 報告的名義採樣率 44,100 和 48,000 Hz。 也有其他採樣率,但很少見。
- 準備好處理由 PROPERTY_OUTPUT_FRAMES_PER_BUFFER 的 getProperty(String) 報告的緩衝區大小。 典型的緩衝區大小包括 96、128、160、192、240、256 或 512 幀,但也有其他值。
最大程度減少輸出延遲時間
創建您的音頻播放器時使用最佳採樣率
要獲得最低延遲時間,您必須提供與設備的最佳採樣率和緩衝區大小匹配的音頻數據。 如需瞭解詳細信息,請參閱面向更低的延遲時間設計。
如下列代碼示例中所示,在 Java 中,您可以從 AudioManager 獲得最佳採樣率:
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); String sampleRateStr = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); int sampleRate = Integer.parseInt(sampleRateStr); if (sampleRate == 0) sampleRate = 44100; // Use a default value if property not found
知道最佳採樣率後,您可以在使用 OpenSL ES 創建您的播放器時提供具體數值:
// create buffer queue audio player void Java_com_example_audio_generatetone_MainActivity_createBufferQueueAudioPlayer (JNIEnv* env, jclass clazz, jint sampleRate, jint framesPerBuffer) { ... // specify the audio source format SLDataFormat_PCM format_pcm; format_pcm.numChannels = 2; format_pcm.samplesPerSec = (SLuint32) sampleRate * 1000; ... }
注:samplesPerSec
指的是每個通道的採樣率,單位爲毫赫(1 Hz = 1000 mHz)。
將音頻數據加入隊列時使用最佳緩衝區大小
您可以通過 AudioManager API 採用與獲得最佳採樣率相似的方式獲得最佳緩衝區大小:
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); int framesPerBufferInt = Integer.parseInt(framesPerBuffer); if (framesPerBufferInt == 0) framesPerBufferInt = 256; // Use default
PROPERTY_OUTPUT_FRAMES_PER_BUFFER
屬性表示 HAL(硬件抽象層)緩衝區可以容納的音頻幀數量。 您應構建音頻緩衝區,使其可以容納這個數量的確切倍數。 如果使用準確數量的音頻幀,會定期出現回調,這將減少抖動。
使用 API 而不是硬編碼值來確定緩衝區大小至關重要,因爲在不同的設備和 Android 版本號中,HAL 緩衝區大小會有所不同。
避免添加涉及信號處理的輸出接口
快速混合器僅支持下列這些接口:
- SL_IID_ANDROIDSIMPLEBUFFERQUEUE
- SL_IID_VOLUME
- SL_IID_MUTESOLO
不支持以下這些接口,因爲它們涉及信號處理,且會導致快速音軌的請求被拒絕:
- SL_IID_BASSBOOST
- SL_IID_EFFECTSEND
- SL_IID_ENVIRONMENTALREVERB
- SL_IID_EQUALIZER
- SL_IID_PLAYBACKRATE
- SL_IID_PRESETREVERB
- SL_IID_VIRTUALIZER
- SL_IID_ANDROIDEFFECT
- SL_IID_ANDROIDEFFECTSEND
創建播放器時,請確保僅添加快速接口,如以下示例所示:
const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
驗證您正在使用低延遲時間音軌
完成下列這些步驟以驗證您是否已成功獲得低延遲時間音軌:
-
- 啓動您的應用,然後運行下列命令:
adb shell ps | grep your_app_name
-
- 記下您應用的進程 ID。
- 現在,從您的應用播放一些音頻。您大約有三秒鐘的時間可以從終端運行下列命令:
adb shell dumpsys media.audio_flinger
- 掃描您的進程 ID。如果您在 Name 列看到 F,表示它在低延遲時間音軌上(F 代表快速音軌)。
最大程度減少預熱延遲時間
第一次將音頻數據加入隊列時,設備音頻電路需要較短、但仍十分重要的一段時間來預熱。 要避免這種預熱延遲時間,您可以將包含無聲的音頻數據緩衝區加入隊列,如以下代碼示例所示:
#define CHANNELS 1 static short* silenceBuffer; int numSamples = frames * CHANNELS; silenceBuffer = malloc(sizeof(*silenceBuffer) * numSamples); for (i = 0; i < numSamples; i++) { silenceBuffer[i] = 0; }
需要生成音頻時,您可以將包含真實音頻數據的緩衝區加入隊列。
注:持續輸出音頻將消耗大量的電量。請確保您在 onPause() 方法中停止輸出。另外,請考慮在用戶無活動的一段時間後暫停無聲輸出。