多媒體隧道

多媒體隧道使壓縮視訊資料能夠透過硬體視訊解碼器直接傳輸到顯示器,而無需經過應用程式碼或 Android 框架程式碼處理。 Android 堆疊下方的裝置特定程式碼透過將視訊幀呈現時間戳記與以下類型的內部時鐘之一進行比較來確定將哪些視訊幀發送到顯示器以及何時發送它們:

  • 對於 Android 5 或更高版本中的點播視訊播放, AudioTrack時鐘與應用程式傳入的音訊演示時間戳同步

  • 對於 Android 11 或更高版本中的直播播放,由調諧器驅動的節目參考時鐘 (PCR) 或系統時間時鐘 (STC)

背景

Android 上的傳統影片播放會在壓縮影片幀解碼後通知應用程式。然後,應用程式將解碼後的視訊幀釋放到顯示器,以便在與對應音訊幀相同的系統時脈時間進行渲染,並擷取歷史AudioTimestamps實例以計算正確的計時。

由於隧道視訊播放繞過應用程式程式碼並減少了作用於視訊的進程數量,因此它可以根據 OEM 實作提供更高效的視訊渲染。它還可以提供更準確的視訊節奏和與所選時鐘(PRC、STC 或音訊)的同步,避免因 Android 渲染視訊請求的時序與真實硬體垂直同步的時序之間潛在偏差而引入的時序問題。但是,隧道也會減少對 GPU 效果的支持,例如畫中畫 (PiP) 視窗中的模糊或圓角,因為緩衝區會繞過 Android 圖形堆疊。

下圖顯示了隧道如何簡化影片播放過程。

傳統模式與隧道模式對比

圖 1.傳統影片播放流程與隧道影片播放流程的比較

對於應用程式開發人員

由於大多數應用程式開發人員都整合了用於播放實作的庫,因此在大多數情況下,實作只需重新配置該庫即可進行隧道播放。對於隧道視訊播放器的低階實現,請使用以下說明。

適用於 Android 5 或更高版本中的點播影片播放:

  1. 建立一個SurfaceView實例。

  2. 建立一個audioSessionId實例。

  3. 使用步驟 2 中建立的audioSessionId實例建立AudioTrackMediaCodec實例。

  4. 使用音訊資料中第一個音訊幀的呈現時間戳將音訊資料排隊到AudioTrack

適用於Android 11或更高版本的直播播放:

  1. 建立一個SurfaceView實例。

  2. Tuner取得avSyncHwId實例。

  3. 使用步驟 2 中建立的avSyncHwId實例建立AudioTrackMediaCodec實例。

API呼叫流程如以下程式碼片段所示:

aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);

// configure for audio clock sync
aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
// or, for tuner clock sync (Android 11 or higher)
new tunerConfig = TunerConfiguration(0, avSyncId);
aab.setTunerConfiguration(tunerConfig);
if (codecName == null) {
  return FAILURE;
}

// configure for audio clock sync
mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
// or, for tuner clock sync (Android 11 or higher)
mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);

點播影片播放的行為

由於隧道點播視訊播放與AudioTrack播放隱式關聯,因此隧道視訊播放的行為可能取決於音訊播放的行為。

  • 在大多數裝置上,預設情況下,在音訊播放開始之前不會渲染視訊幀。但是,應用程式可能需要在開始音訊播放之前渲染視訊幀,例如,在搜尋時向使用者顯示當前視訊位置。

    • 若要指示第一個排隊的視訊畫面在解碼後應立即渲染,請將PARAMETER_KEY_TUNNEL_PEEK參數設為1 。當壓縮視訊幀在佇列中重新排序時(例如,當存在B 幀時),這意味著第一個顯示的視訊幀應始終是 I 幀。

    • 如果您不希望在音訊播放開始之前渲染第一個排隊的視訊幀,請將此參數設為0

    • 如果未設定此參數,則 OEM 將確定設備的行為。

  • 當音訊資料未提供給AudioTrack且緩衝區為空(音訊欠載)時,視訊播放會停止,直到寫入更多音訊數據,因為音訊時脈不再前進。

  • 在播放期間,應用程式無法修正的不連續性可能會出現在音訊演示時間戳中。發生這種情況時,OEM 透過停止當前視訊幀來糾正負間隙,並透過丟棄視訊幀或插入無聲音訊幀(取決於 OEM 實現)來糾正正間隙。對於插入的無聲音訊幀, AudioTimestamp幀位置不會增加。

對於設備製造商

配置

OEM 應該創建一個單獨的視訊解碼器來支援隧道視訊播放。該解碼器應該在media_codecs.xml檔案中聲明它能夠進行隧道播放:

<Feature name="tunneled-playback" required="true"/>

當隧道MediaCodec實例配置了音訊會話 ID 時,它會向AudioFlinger查詢此HW_AV_SYNC ID:

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);
}

在此查詢期間, AudioFlinger從主音訊設備檢索HW_AV_SYNC ID,並在內部將其與音訊會話 ID 關聯起來:

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);

如果已建立AudioTrack實例,則HW_AV_SYNC ID 將傳遞到具有相同音訊會話 ID 的輸出流。如果尚未創建,則HW_AV_SYNC ID 將在AudioTrack創建期間傳遞到輸出流。這是由播放線程完成的:

mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);

HW_AV_SYNC ID,無論它對應於音訊輸出流還是Tuner配置,都會被傳遞到 OMX 或 Codec2 元件中,以便 OEM 程式碼可以將編解碼器與對應的音訊輸出流或調諧器流相關聯。

在元件配置期間,OMX 或 Codec2 元件應傳回一個邊帶句柄,可用於將編解碼器與 Hardware Composer (HWC) 層關聯。當應用程式將 Surface 與MediaCodec關聯時,此邊帶句柄將透過SurfaceFlinger向下傳遞給 HWC,後者將該層配置為邊帶層。

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;
}

HWC 負責在適當的時間從編解碼器輸出接收新的影像緩衝區,與關聯的音訊輸出流或調諧器程式參考時脈同步,將緩衝區與其他層的當前內容合成,並顯示結果影像。這與正常的準備和設定週期無關。只有當其他層變更或邊帶層的屬性(例如位置或大小)變更時,才會發生準備和設定呼叫。

奧米克斯

隧道解碼器元件應支援以下內容:

  • 設定OMX.google.android.index.configureVideoTunnelMode擴充參數,該參數使用ConfigureVideoTunnelModeParams結構體傳入與音訊輸出裝置關聯的HW_AV_SYNC ID。

  • 配置OMX_IndexConfigAndroidTunnelPeek參數,該參數告訴編解碼器渲染或不渲染第一個解碼的視訊幀,無論音訊播放是否已開始。

  • 當第一個隧道視訊幀已解碼並準備好渲染時發送OMX_EventOnFirstTunnelFrameReady事件。

AOSP實作透過OMXNodeInstanceACodec中設定隧道模式,如以下程式碼片段所示:

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;

如果元件支援此配置,它應該為此編解碼器分配一個邊帶句柄,並透過pSidebandWindow成員將其傳回,以便 HWC 可以識別關聯的編解碼器。如果元件不支援此配置,則應將bTunneled設定為OMX_FALSE

編解碼器2

在 Android 11 或更高版本中, Codec2支援隧道播放。解碼器元件應支援以下內容:

  • 配置C2PortTunneledModeTuning ,它配置隧道模式並傳入從音訊輸出設備或調諧器配置檢索的HW_AV_SYNC

  • 查詢C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE ,以指派和擷取 HWC 的邊帶句柄。

  • 連接到C2Work時處理C2_PARAMKEY_TUNNEL_HOLD_RENDER ,指示編解碼器解碼並發出工作完成訊號,但不渲染輸出緩衝區,直到 1) 稍後指示編解碼器渲染它或 2) 音訊播放開始。

  • 處理C2_PARAMKEY_TUNNEL_START_RENDER ,指示編解碼器立即渲染標有C2_PARAMKEY_TUNNEL_HOLD_RENDER的幀,即使音頻播放尚未開始也是如此。

  • 保留debug.stagefright.ccodec_delayed_params未配置(建議)。如果您配置了它,請設定為false

AOSP 實作透過C2PortTunnelModeTuningCCodec中設定隧道模式,如下列程式碼片段所示:

if (msg->findInt32("audio-hw-sync", &tunneledPlayback->m.syncId[0])) {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::AUDIO_HW_SYNC;
} else if (msg->findInt32("hw-av-sync-id", &tunneledPlayback->m.syncId[0])) {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::HW_AV_SYNC;
} else {
    tunneledPlayback->m.syncType =
            C2PortTunneledModeTuning::Struct::sync_type_t::REALTIME;
    tunneledPlayback->setFlexCount(0);
}
c2_status_t c2err = comp->config({ tunneledPlayback.get() }, C2_MAY_BLOCK,
        failures);
std::vector<std::unique_ptr<C2Param>> params;
c2err = comp->query({}, {C2PortTunnelHandleTuning::output::PARAM_TYPE},
        C2_DONT_BLOCK, &params);
if (c2err == C2_OK && params.size() == 1u) {
    C2PortTunnelHandleTuning::output *videoTunnelSideband =
            C2PortTunnelHandleTuning::output::From(params[0].get());
    return OK;
}

如果元件支援此配置,它應該為此編解碼器分配一個邊帶句柄,並透過C2PortTunnelHandlingTuning將其傳回,以便 HWC 可以識別關聯的編解碼器。

音訊哈爾

對於點播視訊播放,音訊 HAL 接收與應用程式寫入的每個音訊資料區塊開頭處的標頭內的大端格式音訊資料內聯的音訊呈現時間戳記:

struct TunnelModeSyncHeader {
  // The 32-bit data to identify the sync header (0x55550002)
  int32 syncWord;
  // The size of the audio data following the sync header before the next sync
  // header might be found.
  int32 sizeInBytes;
  // The presentation timestamp of the first audio sample following the sync
  // header.
  int64 presentationTimestamp;
  // The number of bytes to skip after the beginning of the sync header to find the
  // first audio sample (20 bytes for compressed audio, or larger for PCM, aligned
  // to the channel count and sample size).
  int32 offset;
}

為了使 HWC 渲染視訊幀與相應的音訊幀同步,音訊 HAL 應解析同步標頭並使用呈現時間戳來重新同步播放時鐘與音訊渲染。若要在播放壓縮音訊時重新同步,音訊 HAL 可能需要解析壓縮音訊資料內的元資料以確定其播放持續時間。

暫停支援

Android 5 或更低版本不包含暫停支援。您只能透過 A/V 飢餓來暫停隧道播放,但如果影片的內部緩衝區很大(例如,OMX 元件中有一秒的資料),它會使暫停看起來沒有反應。

在 Android 5.1 或更高版本中, AudioFlinger支援直接(隧道)音訊輸出的暫停和恢復。如果HAL實現了暫停和恢復,則軌道暫停和恢復被轉發到HAL。

透過在播放執行緒中執行 HAL 呼叫來遵守暫停、刷新、恢復呼叫順序(與卸載相同)。

實施建議

音訊哈爾

對於 Android 11,來自 PCR 或 STC 的 HW 同步 ID 可用於 A/V 同步,因此支援僅視訊串流。

對於 Android 10 或更低版本,支援隧道視訊播放的裝置應至少有一個音訊輸出流設定文件,並在其audio_policy.conf檔案中包含FLAG_HW_AV_SYNCAUDIO_OUTPUT_FLAG_DIRECT標誌。這些標誌用於根據音頻時鐘設定係統時鐘。

奧米克斯

設備製造商應該有一個單獨的 OMX 組件用於隧道視訊播放(製造商可以有額外的 OMX 組件用於其他類型的音訊和視訊播放,例如安全播放)。隧道組件應該:

  • 在其輸出埠上指定 0 個緩衝區( nBufferCountMinnBufferCountActual )。

  • 實作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)

當顯示器上有隧道層(具有HWC_SIDEBAND compositionType層)時,該層的sidebandStream是 OMX 視訊元件所指派的邊帶句柄。

HWC 將解碼的視訊框架(來自隧道 OMX 元件)同步到關聯的音軌(帶有audio-hw-sync ID)。當新的視訊畫面成為目前影格時,HWC 會將其與上次準備或設​​定呼叫期間接收到的所有圖層的當前內容進行合成,並顯示結果影像。只有當其他圖層變更或邊帶層的屬性(例如位置或大小)變更時,才會發生準備或設定呼叫。

下圖表示 HWC 與硬體(或核心或驅動程式)同步器一起工作,將視訊幀 (7b) 與最新的合成 (7a) 結合起來,以便基於音訊 (7c) 在正確的時間顯示。

HWC基於音訊組合視訊幀

圖 2. HWC 硬體(或核心或驅動程式)同步器