多媒體隧道

多媒體隧道可讓壓縮的影片資料透過硬體影片解碼器直接傳送至螢幕,而不需要經過應用程式程式碼或 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 的輸出串流。如果尚未建立,則會在建立 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 設定,都會傳遞至 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 事件。

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) 顯示。

HWC 根據音訊結合影片影格

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