マルチメディア トンネリング

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

マルチメディア トンネリングを使用すると、圧縮された動画データを、アプリ コードや Android フレームワーク コードで処理することなく、ハードウェア ビデオ デコーダーを介して直接ディスプレイにトンネリングできます。 Android スタックの下にあるデバイス固有のコードは、ビデオ フレーム プレゼンテーションのタイムスタンプを次のいずれかのタイプの内部クロックと比較して、ディスプレイに送信するビデオ フレームと送信するタイミングを決定します。

  • Android 5 以降でのオンデマンド ビデオ再生の場合、アプリから渡されたオーディオ プレゼンテーションのタイムスタンプに同期されたAudioTrackクロック

  • Android 11 以降でライブ ブロードキャストを再生するには、チューナーによって駆動されるプログラム基準クロック (PCR) またはシステム タイム クロック (STC)

バックグラウンド

Android での従来のビデオ再生では、圧縮されたビデオ フレームがデコードされるとアプリに通知されます。次に、アプリは、デコードされたビデオ フレームをディスプレイにリリースして、対応するオーディオ フレームと同じシステム クロック時間でレンダリングし、過去のAudioTimestampsインスタンスを取得して正しいタイミングを計算します。

トンネル ビデオ再生はアプリ コードをバイパスし、ビデオに作用するプロセスの数を減らすため、OEM の実装によっては、より効率的なビデオ レンダリングを提供できます。また、ビデオをレンダリングするための Android リクエストのタイミングと真のハードウェア vsync のタイミングとの間の潜在的なスキューによって生じるタイミングの問題を回避することで、より正確なビデオ ケイデンスと選択したクロック (PRC、STC、またはオーディオ) への同期を提供できます。ただし、トンネリングは、バッファが Android グラフィック スタックをバイパスするため、ピクチャ イン ピクチャ (PiP) ウィンドウのぼかしや角の丸みなどの GPU 効果のサポートを減らすこともあります。

次の図は、トンネリングによってビデオ再生プロセスがどのように簡素化されるかを示しています。

トラディショナル モードとトンネル モードの比較

図 1.従来の動画再生プロセスとトンネル化された動画再生プロセスの比較

アプリ開発者向け

ほとんどのアプリ開発者は、再生実装用のライブラリと統合するため、ほとんどの場合、実装では、トンネル再生用にそのライブラリを再構成するだけで済みます。トンネル ビデオ プレーヤーの低レベルの実装については、次の手順を使用します。

Android 5 以降でのオンデマンド ビデオ再生の場合:

  1. SurfaceViewインスタンスを作成します。

  2. audioSessionIdインスタンスを作成します。

  3. 手順 2 で作成したaudioSessionIdインスタンスを使用して、 AudioTrackおよびMediaCodecインスタンスを作成します。

  4. オーディオ データの最初のオーディオ フレームのプレゼンテーション タイムスタンプを使用して、オーディオ データをAudioTrackにキューイングします。

Android 11 以降でライブ ブロードキャストを再生する場合:

  1. SurfaceViewインスタンスを作成します。

  2. TunerからavSyncHwIdインスタンスを取得します。

  3. 手順 2 で作成したavSyncHwIdインスタンスを使用して、 AudioTrackおよびMediaCodecインスタンスを作成します。

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 で構成されている場合、このHW_AV_SYNC ID についてAudioFlingerにクエリを実行します。

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

OEM コードが対応するオーディオ出力ストリームまたはチューナー ストリームにコーデックを関連付けることができるように、 HW_AV_SYNC ID は、オーディオ出力ストリームまたはTuner構成に対応するかどうかにかかわらず、OMX または Codec2 コンポーネントに渡されます。

コンポーネントの構成中に、OMX または Codec2 コンポーネントは、コーデックを Hardware Composer (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 と set の呼び出しは、他のレイヤーが変更された場合、またはサイドバンド レイヤーのプロパティ (位置やサイズなど) が変更された場合にのみ発生します。

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;

コンポーネントがこの構成をサポートしている場合、HWC が関連するコーデックを識別できるように、サイドバンド ハンドルをこのコーデックに割り当て、 pSidebandWindowメンバーを介して戻す必要があります。コンポーネントがこの構成をサポートしていない場合、 bTunneledOMX_FALSEに設定する必要があります。

コーデック2

Android 11 以降では、 Codec2はトンネル再生をサポートしています。デコーダ コンポーネントは、次をサポートする必要があります。

  • C2PortTunneledModeTuningを構成します。これは、トンネル モードを構成し、オーディオ出力デバイスまたはチューナー構成から取得したHW_AV_SYNCを渡します。

  • C2_PARAMKEY_OUTPUT_TUNNEL_HANDLEを照会して、HWC のサイドバンド ハンドルを割り当てて取得します。

  • C2_PARAMKEY_TUNNEL_HOLD_RENDERにアタッチされた場合のC2Workの処理。これは、コーデックにデコードして作業の完了を通知するように指示しますが、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;
}

コンポーネントがこの構成をサポートしている場合、HWC が関連するコーデックを識別できるように、サイドバンド ハンドルをこのコーデックに割り当て、 C2PortTunnelHandlingTuningを介して戻す必要があります。

オーディオ 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 が対応するオーディオ フレームと同期してビデオ フレームをレンダリングするには、オーディオ HAL が同期ヘッダーを解析し、プレゼンテーション タイムスタンプを使用して再生クロックをオーディオ レンダリングと再同期する必要があります。圧縮オーディオの再生中に再同期するには、オーディオ HAL が圧縮オーディオ データ内のメタデータを解析して、再生時間を決定する必要がある場合があります。

サポートを一時停止

Android 5 以下には一時停止のサポートが含まれていません。 A/V 不足によってのみトンネル再生を一時停止できますが、ビデオの内部バッファーが大きい場合 (たとえば、OMX コンポーネントに 1 秒間のデータがある場合)、一時停止が応答していないように見えます。

Android 5.1 以降では、 AudioFlingerは、直接 (トンネリングされた) オーディオ出力の一時停止と再開をサポートしています。 HAL が一時停止と再開を実装している場合、追跡の一時停止と再開は HAL に転送されます。

一時停止、フラッシュ、再開の呼び出しシーケンスは、再生スレッドで HAL 呼び出しを実行することによって尊重されます (オフロードと同じ)。

実装の提案

オーディオ HAL

Android 11 の場合、PCR または STC からの HW 同期 ID を A/V 同期に使用できるため、ビデオのみのストリームがサポートされます。

Android 10 以前の場合、トンネル ビデオ再生をサポートするデバイスには、 audio_policy.confファイルにFLAG_HW_AV_SYNCおよびAUDIO_OUTPUT_FLAG_DIRECTフラグを含む少なくとも 1 つのオーディオ出力ストリーム プロファイルが必要です。これらのフラグは、オーディオ クロックからシステム クロックを設定するために使用されます。

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)

ディスプレイにトンネリングされたレイヤー ( HWC_SIDEBAND compositionTypeを持つレイヤー) がある場合、レイヤーのsidebandStreamは、OMX ビデオ コンポーネントによって割り当てられたサイドバンド ハンドルです。

HWC は、デコードされたビデオ フレーム (トンネリングされた OMX コンポーネントから) を関連するオーディオ トラック ( audio-hw-sync ID を使用) に同期します。新しいビデオ フレームが現在のビデオ フレームになると、HWC は最後の準備または設定の呼び出し中に受信したすべてのレイヤーの現在のコンテンツと合成し、結果の画像を表示します。 prepare または set 呼び出しは、他のレイヤーが変更された場合、またはサイドバンド レイヤーのプロパティ (位置やサイズなど) が変更された場合にのみ発生します。

次の図は、ハードウェア (またはカーネルまたはドライバー) シンクロナイザーと連携して、ビデオ フレーム (7b) を最新のコンポジション (7a) と組み合わせて、オーディオ (7c) に基づいて正しい時間に表示する HWC を表しています。

オーディオに基づいてビデオ フレームを結合する HWC

図 2. HWC ハードウェア (またはカーネルまたはドライバー) シンクロナイザー