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

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

  • Android 5 以降のオンデマンド動画再生の場合は、アプリが渡す音声プレゼンテーション タイムスタンプに同期される AudioTrack クロック

  • Android 11 以降のライブ配信再生の場合は、チューナーによって駆動されるプログラム クロック リファレンス(PCR)またはシステムタイム クロック(STC)

背景

圧縮動画フレームがデコードされると、Android 上の従来の動画再生はそれをアプリに通知します。アプリはデコードされた動画フレームをディスプレイにリリースして、対応する音声フレームと同じシステム クロック時間にレンダリングされるようにし、正しい時間を計算するために履歴 AudioTimestamps インスタンスを取得します。

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

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

従来のモードとトンネルモードの比較

図 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 を持つ出力ストリームに渡されます。まだ作成されていない場合、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 コンポーネントは、コーデックを 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 は、適切なタイミングで、(関連する音声出力ストリームまたはチューナー プログラム リファレンス クロックに同期された)新しい画像バッファをコーデック出力から受け取り、このバッファを他のレイヤの現在のコンテンツと合成して、生成された画像を表示します。この処理は、通常の準備と設定のサイクルとは独立して行われます。準備と設定の呼び出しは、他のレイヤが変更された場合、またはサイドバンド レイヤのプロパティ(位置やサイズなど)が変更された場合にのみ行われます。

OMX

トンネリング デコーダ コンポーネントは以下をサポートする必要があります。

  • OMX.google.android.index.configureVideoTunnelMode 拡張パラメータを設定する。このパラメータは、ConfigureVideoTunnelModeParams 構造体を使用して、音声出力デバイスに関連付けられた HW_AV_SYNC ID を渡します。

  • OMX_IndexConfigAndroidTunnelPeek パラメータを構成する。このパラメータは、音声の再生が開始されたかどうかにかかわらず、最初にデコードされた動画フレームをレンダリングするかどうかをコーデックに指示します。

  • 最初にトンネリングされた動画フレームがデコードされてレンダリングの準備ができたときに、OMX_EventOnFirstTunnelFrameReady イベントを送信する。

次のコード スニペットに示すように、AOSP 実装は OMXNodeInstance を介して ACodec でトンネルモードを構成します。

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 に設定する必要があります。

Codec2

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

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

  • C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE をクエリして、HWC のサイドバンド ハンドルの割り当てと取得を行う。

  • C2_PARAMKEY_TUNNEL_HOLD_RENDERC2Work にアタッチされたときにそれを処理する。これにより、コーデックにデコードと作業完了通知を指示する一方で、1)コーデックが後でレンダリングを指示されるまで、または 2)音声の再生が開始されるまで、出力バッファをレンダリングしないように指示します。

  • C2_PARAMKEY_TUNNEL_START_RENDER を処理する。これにより、音声の再生が開始されていなくても、C2_PARAMKEY_TUNNEL_HOLD_RENDER でマークされたフレームを直ちにレンダリングするようコーデックに指示します。

  • debug.stagefright.ccodec_delayed_params を未構成のままにする(推奨)。構成する場合は false に設定します。

次のコード スニペットに示すように、AOSP 実装は C2PortTunnelModeTuning を介して CCodec でトンネルモードを構成します。

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

オンデマンド動画再生の場合、オーディオ 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 では、A/V 同期に PCR または STC からの HW 同期 ID を使用できるため、動画のみのストリームがサポートされています。

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>

Hardware Composer(HWC)

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

HWC は、デコードされた動画フレームを(トンネリング OMX コンポーネントから)関連する音声トラックに(audio-hw-sync ID で)同期します。新しい動画フレームが現在のフレームになると、HWC は、前回の準備または設定呼び出しで受け取ったすべてのレイヤの現在のコンテンツを合成し、生成された画像を表示します。準備または設定呼び出しは、他のレイヤが変更された場合、またはサイドバンド レイヤのプロパティ(位置やサイズなど)が変更された場合にのみ行われます。

次の図は、ハードウェア(またはカーネルあるいはドライバ)シンクロナイザーを処理する HWC を示しています。HWC は、音声(7c)に基づいて動画フレーム(7b)を最新のコンポジション(7a)と結合し、正しいタイミングで表示されるようにします。

音声に基づいて動画フレームを結合する HWC

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