多媒體隧道

您可以在 Android 框架 5.0 及更高版本中實現多媒體隧道。儘管 Android TV 不需要多媒體隧道,但它為超高清 (4K) 內容提供了最佳體驗。

對於 Android 11 或更高版本,您可以使用直接從 Tuner 饋送的音頻和視頻內容來實現多媒體隧道。 Codec2AudioTrack可以使用來自 Tuner 的 HW 同步 ID,它可能對應於程序時鐘參考 (PCR) 或系統時鐘 (STC) 通道。

背景

Android 媒體框架以四種方式處理音頻/視頻內容:

  • 純軟件(本地解碼):應用處理器 (AP) 在本地將音頻解碼為脈衝編碼調製 (PCM),無需特殊加速。始終用於 Ogg Vorbis,並在沒有壓縮卸載支持時用於 MP3 和 AAC。
  • 壓縮音頻卸載將壓縮音頻數據直接發送到數字信號處理器 (DSP) 並儘可能關閉 AP。用於在屏幕關閉時播放音樂文件。
  • 壓縮音頻直通將壓縮音頻(特別是 AC3 和 E-AC3)直接通過 HDMI 發送到外部電視或音頻接收器,而不在 Android TV 設備上對其進行解碼。視頻部分單獨處理。
  • 多媒體隧道將壓縮的音頻和視頻數據一起發送。當視頻和音頻解碼器接收到編碼流時,它不會返回到框架。理想情況下,流不會中斷 AP。
  • 多媒體直通將壓縮的音頻和視頻數據一起從 Tuner 發送到視頻和音頻解碼器,而不涉及框架。
多媒體隧道流程圖
圖 1.多媒體隧道流

方法比較

純軟件壓縮音頻卸載壓縮音頻直通多媒體隧道多媒體直通
解碼位置美聯社數字信號處理器電視或音頻/視頻接收器 (AVR)電視或 AVR電視或 AVR
處理音頻是的是的是的是的
處理視頻是的是的

對於應用程序開發人員

創建SurfaceView實例,獲取音頻會話 ID,然後創建AudioTrackMediaCodec實例,為播放和視頻幀解碼提供必要的時序和配置。

對於 Android 11 或更高版本,作為音頻會話 ID 的替代,應用可以從 Tuner 獲取 HW 同步 ID,並將其提供給AudioTrackMediaCodec實例以進行 A/V 同步。

音視頻同步

在多媒體隧道模式下,音頻和視頻在主時鐘上同步。

  • 對於 Android 11 或更高版本,來自 Tuner 的 PCR 或 STC 可能是 A/V 同步的主時鐘。
  • 對於 Android 10 或更低版本,音頻時鐘是用於 A/V 播放的主時鐘。

如果隧道視頻MediaCodec實例和AudioTrack實例鏈接到 AudioTrack 中的HW_AV_SYNC實例, AudioTrack派生的隱式時鐘會根據實際的音頻或視頻幀呈現時間戳 (PTS) 限制HW_AV_SYNC每個視頻幀和音頻樣本的時間。

API調用流程

對於 Android 11 或更高版本,客戶端可以使用來自 Tuner 的 HW 同步 ID。

  1. 創建一個SurfaceView實例。
    SurfaceView sv = new SurfaceView(mContext);
  2. 獲取音頻會話 ID。此唯一 ID 用於創建音軌 ( AudioTrack )。它被傳遞給媒體編解碼器 ( MediaCodec ) 並被媒體框架用於鏈接音頻和視頻路徑。
    AudioManager am = mContext.getSystemService(AUDIO_SERVICE);
    int audioSessionId = am.generateAudioSessionId()
    // or, for Android 11 or higher
    int avSyncId = tuner.getAvSyncHwId();
  3. 使用 HW A/V 同步AudioAttributes AudioTrack

    音頻策略管理器向硬件抽象層 (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);
    
  4. 創建一個視頻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);
    
  5. 解碼視頻幀。
    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 的順序,如下兩圖所示。

編解碼器配置前創建的音軌示意圖
圖 2.在編解碼器配置之前創建的音軌
編解碼器配置後創建的音軌示意圖
圖 3.編解碼器配置後創建的音軌

對於設備製造商

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 負責在適當的時間從流中接收新的圖像緩衝區(例如,同步到相關的音頻輸出設備),將它們與其他層的當前內容合成,並顯示結果圖像。這與正常的準備/設置週期無關。只有當其他層發生變化或邊帶層的屬性(例如位置或大小)發生變化時,才會調用準備/設置。

配置

框架/av/services/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/services/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 解碼器配置

媒體編解碼器.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將此硬件同步 ID 傳遞給 OMX 隧道視頻解碼器。

編解碼器.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方法配置。

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

編解碼器.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 的 HW 同步 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>

硬件作曲家

當顯示器上有一個隧道層(具有HWC_SIDEBAND compositionType的層)時,該層的sidebandStream是 OMX 視頻組件分配的邊帶句柄。

HW Composer 將解碼的視頻幀(來自隧道 OMX 組件)同步到相關的音軌(使用audio-hw-sync ID)。當一個新的視頻幀變為當前時,HW Composer 將它與在最後一次準備/設置調用期間接收到的所有層的當前內容合成,並顯示結果圖像。只有當其他層發生變化或邊帶層的屬性(如位置或大小)發生變化時,才會發生準備/設置調用。

圖 4 表示 HW Composer 與 HW(或內核/驅動程序)同步器一起工作,以根據音頻 (7c) 將視頻幀 (7b) 與最新合成 (7a) 組合在一起,以便在正確的時間顯示。

基於音頻組合視頻幀的硬件合成器示意圖
圖 4. HW Composer 與 HW(或內核/驅動程序)同步器一起工作