マルチメディア トンネリングを使用すると、圧縮動画データをハードウェア動画デコーダ経由で直接ディスプレイにトンネリングできます。アプリコードまたは Android フレームワーク コードで処理する必要はありません。Android スタックの下にあるデバイス固有のコードは、動画フレームのプレゼンテーション タイムスタンプを次のいずれかのタイプの内部クロックと比較して、ディスプレイに送信する動画フレームとそれらを送信するタイミングを決定します。
Android 5 以降のオンデマンド動画再生の場合は、アプリが渡す音声プレゼンテーション タイムスタンプに同期される
AudioTrack
クロックAndroid 11 以降のライブ配信再生の場合は、チューナーによって駆動されるプログラム クロック リファレンス(PCR)またはシステムタイム クロック(STC)
背景
圧縮動画フレームがデコードされると、Android 上の従来の動画再生はそれをアプリに通知します。アプリはデコードされた動画フレームをディスプレイにリリースして、対応する音声フレームと同じシステム クロック時間にレンダリングされるようにし、正しい時間を計算するために履歴 AudioTimestamps
インスタンスを取得します。
トンネリング動画再生はアプリコードをバイパスし、動画を処理するプロセスの数を削減するので、OEM の実装に応じて動画をより効率的にレンダリングできます。また、動画をレンダリングする Android リクエストのタイミングと実際のハードウェア vsync のタイミングの間の潜在的なずれによって生じるタイミングの問題を回避して、より正確な動画ケイデンスと、選択したクロック(PCR、STC、またはオーディオ)への同期を提供できます。ただし、トンネリングによって、ピクチャー イン ピクチャー(PIP)ウィンドウで GPU の効果(ぼかしや角丸など)のサポートの質が低下することもあります。これは、バッファが Android グラフィック スタックをバイパスするためです。
次の図は、トンネリングによって動画再生プロセスがどのように簡素化されるかを示しています。
図 1. 従来の動画再生プロセスとトンネリング動画再生プロセスの比較
アプリ デベロッパー向けの情報
ほとんどのアプリ デベロッパーは再生を実装するためのライブラリを統合しているので、多くの場合、実装ではトンネリング再生用にそのライブラリを再構成するだけで済みます。トンネリング動画プレーヤーの低レベルの実装では、次の手順を使用します。
Android 5 以降のオンデマンド動画再生の場合:
SurfaceView
インスタンスを作成します。audioSessionId
インスタンスを作成します。ステップ 2 で作成した
audioSessionId
インスタンスを使用して、AudioTrack
インスタンスとMediaCodec
インスタンスを作成します。音声データにおける最初の音声フレームのプレゼンテーション タイムスタンプを使用して、音声データを
AudioTrack
のキューに追加します。
Android 11 以降のライブ配信再生の場合:
SurfaceView
インスタンスを作成します。Tuner
からavSyncHwId
インスタンスを取得します。ステップ 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
メンバーを介してそれを戻す必要があります。コンポーネントがこの構成をサポートしていない場合、コンポーネントは bTunneled
を OMX_FALSE
に設定する必要があります。
Codec2
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 実装は 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, ¶ms);
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 バッファ(
nBufferCountMin
、nBufferCountActual
)を指定する。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)と結合し、正しいタイミングで表示されるようにします。
図 2. HWC のハードウェア(またはカーネルあるいはドライバ)シンクロナイザー