您可以在 Android 框架 5.0 及更高版本中實現多媒體隧道。儘管 Android TV 不需要多媒體隧道,但它為超高清 (4K) 內容提供了最佳體驗。
對於 Android 11 或更高版本,您可以使用直接從 Tuner 饋送的音頻和視頻內容來實現多媒體隧道。 Codec2
和AudioTrack
可以使用來自 Tuner 的 HW 同步 ID,它可能對應於程序時鐘參考 (PCR) 或系統時鐘 (STC) 通道。
背景
Android 媒體框架以四種方式處理音頻/視頻內容:
- 純軟件(本地解碼):應用處理器 (AP) 在本地將音頻解碼為脈衝編碼調製 (PCM),無需特殊加速。始終用於 Ogg Vorbis,並在沒有壓縮卸載支持時用於 MP3 和 AAC。
- 壓縮音頻卸載將壓縮音頻數據直接發送到數字信號處理器 (DSP) 並儘可能關閉 AP。用於在屏幕關閉時播放音樂文件。
- 壓縮音頻直通將壓縮音頻(特別是 AC3 和 E-AC3)直接通過 HDMI 發送到外部電視或音頻接收器,而不在 Android TV 設備上對其進行解碼。視頻部分單獨處理。
- 多媒體隧道將壓縮的音頻和視頻數據一起發送。當視頻和音頻解碼器接收到編碼流時,它不會返回到框架。理想情況下,流不會中斷 AP。
- 多媒體直通將壓縮的音頻和視頻數據一起從 Tuner 發送到視頻和音頻解碼器,而不涉及框架。
方法比較
純軟件 | 壓縮音頻卸載 | 壓縮音頻直通 | 多媒體隧道 | 多媒體直通 | |
---|---|---|---|---|---|
解碼位置 | 美聯社 | 數字信號處理器 | 電視或音頻/視頻接收器 (AVR) | 電視或 AVR | 電視或 AVR |
處理音頻 | 是的 | 是的 | 是的 | 是的 | 不 |
處理視頻 | 是的 | 不 | 不 | 是的 | 不 |
對於應用程序開發人員
創建SurfaceView
實例,獲取音頻會話 ID,然後創建AudioTrack
和MediaCodec
實例,為播放和視頻幀解碼提供必要的時序和配置。
對於 Android 11 或更高版本,作為音頻會話 ID 的替代,應用可以從 Tuner 獲取 HW 同步 ID,並將其提供給AudioTrack
和MediaCodec
實例以進行 A/V 同步。
音視頻同步
在多媒體隧道模式下,音頻和視頻在主時鐘上同步。
- 對於 Android 11 或更高版本,來自 Tuner 的 PCR 或 STC 可能是 A/V 同步的主時鐘。
- 對於 Android 10 或更低版本,音頻時鐘是用於 A/V 播放的主時鐘。
如果隧道視頻MediaCodec
實例和AudioTrack
實例鏈接到 AudioTrack 中的HW_AV_SYNC
實例, AudioTrack
派生的隱式時鐘會根據實際的音頻或視頻幀呈現時間戳 (PTS) 限制HW_AV_SYNC
每個視頻幀和音頻樣本的時間。
API調用流程
對於 Android 11 或更高版本,客戶端可以使用來自 Tuner 的 HW 同步 ID。
- 創建一個
SurfaceView
實例。SurfaceView sv = new SurfaceView(mContext);
- 獲取音頻會話 ID。此唯一 ID 用於創建音軌 (
AudioTrack
)。它被傳遞給媒體編解碼器 (MediaCodec
) 並被媒體框架用於鏈接音頻和視頻路徑。AudioManager am = mContext.getSystemService(AUDIO_SERVICE); int audioSessionId = am.generateAudioSessionId() // or, for Android 11 or higher int avSyncId = tuner.getAvSyncHwId();
- 使用 HW A/V 同步
AudioAttributes
AudioTrack
音頻策略管理器向硬件抽象層 (HAL) 請求支持
FLAG_HW_AV_SYNC
的設備輸出,並創建一個直接連接到此輸出的音軌,無需中間混音器。AudioAttributes.Builder aab = new AudioAttributes.Builder(); aab.setUsage(AudioAttributes.USAGE_MEDIA); aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE); aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC); // or, for Android 11 or higher new tunerConfig = TunerConfiguration(0, avSyncId); aab.setTunerConfiguration(tunerConfig); AudioAttributes aa = aab.build(); AudioTrack at = new AudioTrack(aa);
- 創建一個視頻
MediaCodec
實例並將其配置為隧道視頻播放。// retrieve codec with tunneled video playback feature MediaFormat mf = MediaFormat.createVideoFormat(“video/hevc”, 3840, 2160); mf.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS); String codecName = mcl.findDecoderForFormat(mf); if (codecName == null) { return FAILURE; } // create codec and configure it mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId); // or, for Android 11 or higher mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId); MediaCodec mc = MediaCodec.createCodecByName(codecName); mc.configure(mf, sv.getSurfaceHolder().getSurface(), null, 0);
- 解碼視頻幀。
mc.start(); for (;;) { int ibi = mc.dequeueInputBuffer(timeoutUs); if (ibi >= 0) { ByteBuffer ib = mc.getInputBuffer(ibi); // fill input buffer (ib) with valid data ... mc.queueInputBuffer(ibi, ...); } // no need to dequeue explicitly output buffers. The codec // does this directly to the sideband layer. } mc.stop(); mc.release(); mc = null;
注意:您可以在此過程中切換步驟 3 和 4 的順序,如下兩圖所示。
對於設備製造商
OEM 應該創建一個單獨的視頻解碼器OpenMAX IL (OMX)組件來支持隧道視頻播放。此 OMX 組件必須宣傳它能夠進行隧道播放(在media_codecs.xml
中)。
<Feature name=”tunneled-playback” required=”true” />
該組件還必須支持使用ConfigureVideoTunnelModeParams
結構的 OMX 擴展參數OMX.google.android.index.configureVideoTunnelMode
。
struct ConfigureVideoTunnelModeParams { OMX_U32 nSize; // IN OMX_VERSIONTYPE nVersion; // IN OMX_U32 nPortIndex; // IN OMX_BOOL bTunneled; // IN/OUT OMX_U32 nAudioHwSync; // IN OMX_PTR pSidebandWindow; // OUT };
當創建隧道MediaCodec
請求時,框架將 OMX 組件配置為隧道模式(通過將bTunneled
設置為OMX_TRUE
)並將使用AUDIO_HW_AV_SYNC
標誌創建的關聯音頻輸出設備傳遞給 OMX 組件(在nAudioHwSync
中)。
如果組件支持這種配置,它應該為這個編解碼器分配一個邊帶句柄,並通過pSidebandWindow
成員將它傳回。邊帶句柄是隧道層的 ID 標籤,可讓Hardware Composer (HW Composer)識別它。如果組件不支持此配置,則應將bTunneled
設置為OMX_FALSE
。
框架檢索由 OMX 組件分配的隧道層(邊帶句柄)並將其傳遞給 HW Composer。該層的compositionType
設置為HWC_SIDEBAND
。 (參見hardware/libhardware/include/hardware/hwcomposer.h
。)
HW Composer 負責在適當的時間從流中接收新的圖像緩衝區(例如,同步到相關的音頻輸出設備),將它們與其他層的當前內容合成,並顯示結果圖像。這與正常的準備/設置週期無關。只有當其他層發生變化或邊帶層的屬性(例如位置或大小)發生變化時,才會調用準備/設置。
配置
框架/av/services/audioflinger/AudioFlinger.cpp
HAL 將HW_AV_SYNC
ID 作為 64 位整數的字符串十進製表示形式返回。 (參見frameworks/av/services/audioflinger/AudioFlinger.cpp
。)
audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice(); char *reply = dev->get_parameters(dev, AUDIO_PARAMETER_HW_AV_SYNC); AudioParameter param = AudioParameter(String8(reply)); int hwAVSyncId; param.getInt(String8(AUDIO_PARAMETER_HW_AV_SYNC), hwAVSyncId);
框架/av/services/audioflinger/Threads.cpp
音頻框架必須找到該會話 ID 對應的 HAL 輸出流,並使用set_parameters
查詢 HAL 中的hwAVSyncId
。
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC=hwAVSyncId);
OMX 解碼器配置
媒體編解碼器.java
音頻框架為此會話 ID 找到相應的 HAL 輸出流,並通過使用get_parameters
查詢 HAL 的AUDIO_PARAMETER_STREAM_HW_AV_SYNC
標誌來檢索audio-hw-sync
ID。
// Retrieve HW AV sync audio output device from Audio Service // in MediaCodec.configure() if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) { int sessionId = 0; try { sessionId = (Integer)entry.getValue(); } catch (Exception e) { throw new IllegalArgumentException("Wrong Session ID Parameter!"); } keys[i] = "audio-hw-sync"; values[i] = AudioSystem.getAudioHwSyncForSession(sessionId); } // ...
使用自定義參數OMX.google.android.index.configureVideoTunnelMode
將此硬件同步 ID 傳遞給 OMX 隧道視頻解碼器。
編解碼器.cpp
在您獲得音頻硬件同步 ID 後,ACodec 使用它來配置隧道視頻解碼器,以便隧道視頻解碼器知道要同步哪個音軌。
// Assume you're going to use tunneled video rendering. // Configure OMX component in tunneled mode and grab sideband handle (sidebandHandle) from OMX // component. native_handle_t* sidebandHandle; // Configure OMX component in tunneled mode status_t err = mOMX->configureVideoTunnelMode(mNode, kPortIndexOutput, OMX_TRUE, audioHwSync, &sidebandHandle);
OMXNodeInstance.cpp
OMX 組件由上面的configureVideoTunnelMode
方法配置。
// paraphrased OMX_INDEXTYPE index; OMX_STRING name = const_cast<OMX_STRING>( "OMX.google.android.index.configureVideoTunnelMode"); OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index); ConfigureVideoTunnelModeParams tunnelParams; InitOMXParams(&tunnelParams); tunnelParams.nPortIndex = portIndex; tunnelParams.bTunneled = tunneled; tunnelParams.nAudioHwSync = audioHwSync; err = OMX_SetParameter(mHandle, index, &tunnelParams); err = OMX_GetParameter(mHandle, index, &tunnelParams); sidebandHandle = (native_handle_t*)tunnelParams.pSidebandWindow;
編解碼器.cpp
在 OMX 組件配置為隧道模式後,邊帶句柄與渲染表面相關聯。
err = native_window_set_sideband_stream(nativeWindow.get(), sidebandHandle); if (err != OK) { ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).", sidebandHandle, err); return err; }
然後最大分辨率提示(如果存在)被發送到組件。
// Configure max adaptive playback resolution - as for any other video decoder int32_t maxWidth = 0, maxHeight = 0; if (msg->findInt32("max-width", &maxWidth) && msg->findInt32("max-height", &maxHeight)) { err = mOMX->prepareForAdaptivePlayback( mNode, kPortIndexOutput, OMX_TRUE, maxWidth, maxHeight); }
暫停支持
Android 5.0 及更低版本不包括暫停支持。您只能通過 A/V 飢餓來暫停隧道播放,但如果視頻的內部緩衝區很大(例如,OMX 組件中有 1 秒的數據),它會使暫停看起來沒有響應。
在 Android 5.1 及更高版本中,AudioFlinger 支持直接(隧道)音頻輸出的暫停和恢復。如果 HAL 實現了暫停/恢復,則將軌道暫停/恢復轉發給 HAL。
通過在播放線程中執行 HAL 調用(與卸載相同)來遵守暫停、刷新、恢復調用順序。
編解碼器2支持
對於 Android 11 或更高版本,Codec2 支持隧道播放。
CCodec.cpp
為了支持隧道播放,Codec2 的工作方式與 OMX 類似。為了支持 Tuner 的 HW 同步 ID,Codec2 從以下來源中查找同步 ID。
sp<ANativeWindow> nativeWindow = static_cast<ANativeWindow *>(surface.get()); int32_t audioHwSync = 0; if (!msg->findInt32("hw-av-sync-id", &audioHwSync)) { if (!msg->findInt32("audio-hw-sync", &audioHwSync)) { } } err = configureTunneledVideoPlayback(comp, audioHwSync, nativeWindow);
實施建議
音頻 HAL
對於 Android 11,來自 PCR 或 STC 的 HW 同步 ID 可用於 A/V 同步,因此支持僅視頻流。
對於 Android 10 或更低版本,支持隧道視頻播放的設備應在其audio_policy.conf
文件中至少具有一個帶有標誌FLAG_HW_AV_SYNC
和AUDIO_OUTPUT_FLAG_DIRECT
的音頻輸出流配置文件。這些標誌用於從音頻時鐘設置系統時鐘。
OMX
設備製造商應該有一個單獨的 OMX 組件用於隧道視頻播放。製造商可以為其他類型的音頻和視頻播放提供額外的 OMX 組件,例如安全播放。
該組件應在其輸出端口上指定 0 個緩衝區( nBufferCountMin
、 nBufferCountActual
)。
隧道組件還必須實現OMX.google.android.index.prepareForAdaptivePlayback setParameter
擴展。
隧道組件必須在media_codecs.xml
文件中指定其功能並聲明隧道播放功能。它還應闡明對幀大小、對齊方式或比特率的任何限制。
<MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC.tunneled"
type="video/avc" >
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" required=”true” />
<Limit name="size" min="32x32" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="bitrate" range="1-20000000" />
...
</MediaCodec>
如果使用相同的 OMX 組件來支持隧道和非隧道解碼,那麼它應該將隧道播放功能保留為非必需。隧道和非隧道解碼器都具有相同的能力限制。
<MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC" type="video/avc" > <Feature name="adaptive-playback" /> <Feature name="tunneled-playback" /> <Limit name="size" min="32x32" max="3840x2160" /> <Limit name="alignment" value="2x2" /> <Limit name="bitrate" range="1-20000000" /> ... </MediaCodec>
硬件作曲家
當顯示器上有一個隧道層(具有HWC_SIDEBAND
compositionType
的層)時,該層的sidebandStream
是 OMX 視頻組件分配的邊帶句柄。
HW Composer 將解碼的視頻幀(來自隧道 OMX 組件)同步到相關的音軌(使用audio-hw-sync
ID)。當一個新的視頻幀變為當前時,HW Composer 將它與在最後一次準備/設置調用期間接收到的所有層的當前內容合成,並顯示結果圖像。只有當其他層發生變化或邊帶層的屬性(如位置或大小)發生變化時,才會發生準備/設置調用。
圖 4 表示 HW Composer 與 HW(或內核/驅動程序)同步器一起工作,以根據音頻 (7c) 將視頻幀 (7b) 與最新合成 (7a) 組合在一起,以便在正確的時間顯示。