マルチメディア トンネリングを使用すると、圧縮された動画データを、アプリ コードや Android フレームワーク コードで処理することなく、ハードウェア ビデオ デコーダーを介して直接ディスプレイにトンネリングできます。 Android スタックの下にあるデバイス固有のコードは、ビデオ フレーム プレゼンテーションのタイムスタンプを次のいずれかのタイプの内部クロックと比較して、ディスプレイに送信するビデオ フレームと送信するタイミングを決定します。
Android 5 以降でのオンデマンド ビデオ再生の場合、アプリから渡されたオーディオ プレゼンテーションのタイムスタンプに同期された
AudioTrack
クロックAndroid 11 以降でライブ ブロードキャストを再生するには、チューナーによって駆動されるプログラム基準クロック (PCR) またはシステム タイム クロック (STC)
バックグラウンド
Android での従来のビデオ再生では、圧縮されたビデオ フレームがデコードされるとアプリに通知されます。次に、アプリは、デコードされたビデオ フレームをディスプレイにリリースして、対応するオーディオ フレームと同じシステム クロック時間でレンダリングし、過去のAudioTimestamps
インスタンスを取得して正しいタイミングを計算します。
トンネル ビデオ再生はアプリ コードをバイパスし、ビデオに作用するプロセスの数を減らすため、OEM の実装によっては、より効率的なビデオ レンダリングを提供できます。また、ビデオをレンダリングするための Android リクエストのタイミングと真のハードウェア vsync のタイミングとの間の潜在的なスキューによって生じるタイミングの問題を回避することで、より正確なビデオ ケイデンスと選択したクロック (PRC、STC、またはオーディオ) への同期を提供できます。ただし、トンネリングは、バッファが Android グラフィック スタックをバイパスするため、ピクチャ イン ピクチャ (PiP) ウィンドウのぼかしや角の丸みなどの GPU 効果のサポートを減らすこともあります。
次の図は、トンネリングによってビデオ再生プロセスがどのように簡素化されるかを示しています。
図 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 で出力ストリームに渡されます。まだ作成されていない場合は、 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
メンバーを介して戻す必要があります。コンポーネントがこの構成をサポートしていない場合、 bTunneled
をOMX_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, ¶ms);
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 バッファー (
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>
ハードウェア コンポーザー (HWC)
ディスプレイにトンネリングされたレイヤー ( HWC_SIDEBAND
compositionType
を持つレイヤー) がある場合、レイヤーのsidebandStream
は、OMX ビデオ コンポーネントによって割り当てられたサイドバンド ハンドルです。
HWC は、デコードされたビデオ フレーム (トンネリングされた OMX コンポーネントから) を関連するオーディオ トラック ( audio-hw-sync
ID を使用) に同期します。新しいビデオ フレームが現在のビデオ フレームになると、HWC は最後の準備または設定の呼び出し中に受信したすべてのレイヤーの現在のコンテンツと合成し、結果の画像を表示します。 prepare または set 呼び出しは、他のレイヤーが変更された場合、またはサイドバンド レイヤーのプロパティ (位置やサイズなど) が変更された場合にのみ発生します。
次の図は、ハードウェア (またはカーネルまたはドライバー) シンクロナイザーと連携して、ビデオ フレーム (7b) を最新のコンポジション (7a) と組み合わせて、オーディオ (7c) に基づいて正しい時間に表示する HWC を表しています。
図 2. HWC ハードウェア (またはカーネルまたはドライバー) シンクロナイザー