多媒體隧道

多媒體隧道可讓壓縮的影片資料透過硬體影片解碼器直接傳送至螢幕,而不需要經過應用程式程式碼或 Android 架構程式碼處理。Android 堆疊下方的裝置專屬程式碼會比較影片影格呈現時間戳記與下列任一類型的內部時鐘,藉此決定要將哪些影片影格傳送至螢幕,以及何時傳送:

  • 針對 Android 5 以上版本的隨選影片播放功能,AudioTrack 時鐘會與應用程式傳送的音訊播放時間戳記同步

  • 針對 Android 11 以上版本的即時播送,調諧器驅動的節目參考時鐘 (PCR) 或系統時間時鐘 (STC)

背景

在 Android 上播放傳統影片時,系統會在解碼壓縮的影片影格時通知應用程式。接著,應用程式會釋出解碼的影片影格,以便在與對應音訊影格相同的系統時鐘時間算繪,並擷取歷來AudioTimestamps 個別事件,以便計算正確的時間。

由於通道式影片播放會略過應用程式的程式碼,並減少對影片所執行的程序數量,因此可依據原始設備製造商 (OEM) 實作項目,提高影片算繪效率。它還能避免 Android 要求轉譯影片的時間與實際硬體 vsync 時間之間的時間差異,進而提供更準確的影片節奏,並與所選時鐘 (PRC、STC 或音訊) 同步。不過,因為緩衝區會略過 Android 圖形堆疊,因此隧道連線也可能會減少 GPU 效果的支援功能,例如模糊或子母畫面 (PiP) 視窗中的圓角。

下圖說明如何透過隧道化簡化影片播放程序。

傳統與隧道模式的比較

圖 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 影格位置。

裝置製造商

設定

原始設備製造商應建立獨立的影片解碼器,以支援隧道式影片播放。這個解碼器應公告其可在 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 元件應傳回側帶句柄,可用於將編解碼器與硬體編譯器 (HWC) 層建立關聯。當應用程式將介面與 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 負責在適當的時間接收轉碼器輸出內容中的新影像緩衝區,無論是同步處理至相關聯的音訊輸出串流或調音器程式參考時鐘、將緩衝區與其他圖層的目前內容進行同步,然後顯示產生的圖片。這與一般準備和設定週期無關。只有當其他圖層變更,或側邊頻層的屬性 (例如位置或大小) 變更時,才會發生 Prepare 和設定呼叫。

OMX

隧道解碼器元件應支援下列項目:

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

  • 設定 OMX_IndexConfigAndroidTunnelPeek 參數,指示編解碼器是否要轉譯第一個解碼的影片影格,不論音訊是否已開始播放。

  • 在第一個通道的影片影格已解碼且準備算繪時,傳送 OMX_EventOnFirstTunnelFrameReady 事件。

Android 開放原始碼計畫實作項目會透過 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

Android 開放原始碼計畫實作項目會透過 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

針對隨選影片播放功能,Audio 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 以與對應音訊影格同步的方式算繪影片影格,Audio HAL 應剖析同步標頭,並使用呈現時間戳記,將播放時鐘與音訊算繪重新同步。如要重新同步處理壓縮音訊的播放作業,Audio HAL 可能需要剖析壓縮音訊資料中的中繼資料,以便判斷播放時間長度。

暫停支援

Android 5 以下版本不支援暫停功能。您只能透過 A/V 飢餓狀態暫停隧道式播放,但如果影片的內部緩衝區太大 (例如 OMX 元件中有 1 秒的資料),就會讓暫停功能看起來無回應。

在 Android 5.1 以上版本中,AudioFlinger 支援直接 (隧道) 音訊輸出的暫停和繼續播放功能。如果 HAL 實作暫停和恢復功能,系統會將追蹤暫停和恢復功能轉送至 HAL。

在播放執行緒中執行 HAL 呼叫 (與卸載相同),系統會遵循暫停、清除、繼續呼叫序列。

實作建議

音訊 HAL

針對 Android 11,PCR 或 STC 的硬體同步 ID 可用於 A/V 同步,因此支援僅限影片串流。

如果是搭載 Android 10 以下版本的裝置,支援通道影片播放的裝置應至少有一個音訊輸出串流設定檔,其 audio_policy.conf 檔案中須含有 FLAG_HW_AV_SYNCAUDIO_OUTPUT_FLAG_DIRECT 旗標。這些標記可用於從音訊時鐘設定系統時鐘。

OMX

裝置製造商應為隧道式影片播放提供獨立的 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>

硬體 Composer (HWC)

如果螢幕上有隧道層 (帶有 HWC_SIDEBAND compositionType 的圖層),圖層的 sidebandStream 就是 OMX 影片元件分配的側邊頻帶控點。

HWC 會將解碼的視訊影格 (來自隧道 OMX 元件) 與相關音訊軌道 (具有 audio-hw-sync ID) 同步。當新的視訊影格符合最新時,HWC 會將這個影格與上次進行準備或設定呼叫時接收的所有圖層內容合併,然後顯示產生的圖片。只有在其他圖層變更,或側帶圖層的屬性 (例如位置或大小) 變更時,才會發生準備或設定呼叫。

下圖代表 HWC 與硬體 (或核心/驅動程式) 同步器搭配運作,將影片影格 (7b) 與最新的組合 (7a) 結合,以便在正確的時間 (7c) 顯示。

根據音訊結合影片影格

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