您可以在Android Framework 5.0及更高版本中實現多媒體隧道。儘管Android TV不需要多媒體隧道,但它為超高清(4K)內容提供了最佳體驗。
對於Android 11或更高版本,您可以使用直接從Tuner饋送的音頻和視頻內容來實現多媒體隧道。 Codec2
和AudioTrack
可以使用來自Tuner的硬件同步ID,該ID可能對應於程序時鐘參考(PCR)或系統時間時鐘(STC)通道。
背景
Android媒體框架以四種方式處理音頻/視頻內容:
- 純軟件(本地解碼):應用處理器(AP)在不進行特殊加速的情況下將音頻本地解碼為脈衝編碼調製(PCM)。在沒有壓縮卸載支持的情況下,始終用於Ogg Vorbis,並用於MP3和AAC。
- 壓縮音頻卸載將壓縮音頻數據直接發送到數字信號處理器(DSP),並儘可能使AP保持關閉狀態。用於在屏幕關閉的情況下播放音樂文件。
- 壓縮音頻直通直接通過HDMI將壓縮音頻(特別是AC3和E-AC3)直接發送到外部電視或音頻接收器,而無需在Android TV設備上對其進行解碼。視頻部分是單獨處理的。
- 多媒體隧道將壓縮的音頻和視頻數據一起發送。當視頻和音頻解碼器接收到已編碼的流時,它不會返回框架。理想情況下,流不會中斷AP。
- 多媒體直通將壓縮的音頻和視頻數據從Tuner一起發送到視頻和音頻解碼器,而無需使用該框架。

方法比較
純軟件 | 壓縮音頻卸載 | 壓縮音頻直通 | 多媒體隧道 | 多媒體直通 | |
---|---|---|---|---|---|
解碼位置 | 美聯社 | 數字信號處理器 | 電視或音頻/視頻接收器(AVR) | 電視或AVR | 電視或AVR |
處理音頻 | 是 | 是 | 是 | 是 | 沒有 |
處理視頻 | 是 | 沒有 | 沒有 | 是 | 沒有 |
對於應用程序開發人員
創建一個SurfaceView
實例,獲取音頻會話ID,然後創建AudioTrack
和MediaCodec
實例,以提供播放和視頻幀解碼所需的時序和配置。
對於Android 11或更高版本,作為音頻會話ID的替代,應用程序可以從Tuner獲取硬件同步ID,並將其提供給AudioTrack
和MediaCodec
實例進行A / V同步。
音視頻同步
在多媒體隧道模式下,音頻和視頻在主時鐘上同步。
- 對於Android 11或更高版本,來自Tuner的PCR或STC可能是A / V同步的主時鐘。
- 對於Android 10或更低版本,音頻時鐘是用於A / V播放的主時鐘。
如果隧道視頻MediaCodec
實例和AudioTrack
實例鏈接到HW_AV_SYNC
在實例AudioTrack
,衍生自所述隱式時鐘HW_AV_SYNC
限制時每個視頻幀和音頻樣本被呈現,根據實際的音頻或視頻幀顯現時間標記(PTS)。
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();
- 創建
AudioTrack
與HW A / V同步AudioAttributes
。音頻策略管理器向硬件抽象層(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負責在適當的時間從流中接收新的圖像緩衝區(例如,同步到關聯的音頻輸出設備),將它們與其他層的當前內容進行組合,並顯示結果圖像。這與正常的準備/固化週期無關。僅當其他層更改或邊帶層的屬性(例如位置或大小)更改時,才進行prepare / set調用。
組態
框架/ av /服務/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 /服務/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解碼器配置
MediaCodec.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
將此HW同步ID傳遞到OMX隧道視頻解碼器。
ACodec.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
方法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;
ACodec.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的硬件同步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會將其與上一次準備/設置調用期間接收到的所有圖層的當前內容進行合成,並顯示結果圖像。僅當其他層發生更改或邊帶層的屬性(例如位置或大小)發生更改時,才會進行prepare / set調用。
圖4表示HW Composer與HW(或內核/驅動程序)同步器一起工作,將視頻幀(7b)與最新的合成(7a)組合在一起,以便基於音頻(7c)在正確的時間顯示。
