多媒體通道

多媒體通道可讓壓縮的影片資料直接透過硬體影片解碼器傳輸到螢幕,不必經過應用程式碼或 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 提供音訊資料,且緩衝區為空 (音訊不足),視訊播放就會停止,直到寫入更多音訊資料為止,因為音訊時鐘不再前進。

  • 播放期間,音訊呈現時間戳記可能會出現應用程式無法修正的不連續情況。發生這種情況時,原始設備製造商會暫停目前的視訊影格,藉此修正負向間隙;如果是正向間隙,則會捨棄視訊影格或插入無聲音訊影格 (視原始設備製造商的實作方式而定)。插入靜音音訊影格時,AudioTimestamp 影格位置不會增加。

裝置製造商

設定

OEM 應建立獨立的影片解碼器,支援通道化影片播放。 這個解碼器應在 media_codecs.xml 檔案中宣傳其支援通道式播放:

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

如果以音訊工作階段 ID 設定通道式 MediaCodec 執行個體,系統會向 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。如果尚未建立,系統會在建立 AudioTrack 時,將 HW_AV_SYNC ID 傳遞至輸出串流。這項作業是由播放執行緒完成:

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

HW_AV_SYNC ID (無論是音訊輸出串流或 Tuner 設定對應的 ID) 會傳遞至 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 負責在適當時間接收編解碼器輸出內容中的新圖像緩衝區,並與相關聯的音訊輸出串流或調諧器程式參考時鐘同步處理,將緩衝區與其他圖層的目前內容合成,然後顯示產生的圖像。這與一般的準備和設定週期無關。只有在其他圖層變更,或側帶圖層的屬性 (例如位置或大小) 變更時,才會發生準備和設定呼叫。

OMX

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

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

  • 設定 OMX_IndexConfigAndroidTunnelPeek 參數,告知轉碼器是否要算繪第一個解碼的影片影格,無論音訊播放是否已開始。

  • 在第一個通道化影片影格解碼完成並準備好轉譯時,傳送 OMX_EventOnFirstTunnelFrameReady 事件。

AOSP 實作會在 ACodec 中透過 OMXNodeInstance 設定通道模式,如以下程式碼片段所示:

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

Codec2

在 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 實作會在 CCodec 中透過 C2PortTunnelModeTuning 設定通道模式,如下列程式碼片段所示:

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

如果是隨選視訊播放,音訊 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 呼叫 (與卸載相同),因此會遵守暫停、清除、繼續呼叫序列。

導入建議

音訊 HAL

如果是 Android 11,則可使用 PCR 或 STC 的硬體同步 ID 進行影音同步,因此支援僅含影片的串流。

如果是 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 如何與硬體 (或核心或驅動程式) 同步器搭配運作,根據音訊 (7c) 將視訊影格 (7b) 與最新合成內容 (7a) 合併,以便在正確時間顯示。

HWC 根據音訊合併視訊影格

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